Exceptions, Contracts, and Reliable ProgrammingIntroductionA program is reliable when it behaves predictably not only during normal execution, but also when inputs are invalid, files are unavailable, networks fail, resources are exhausted, or internal assumptions are violated. Object-oriented programming in Java provides several mechanisms for expressing and managing such situations: exceptions, assertions, contracts, defensive checks, resource-management constructs, and logging. These mechanisms are related but not interchangeable. An exception communicates a failure that code may be able to handle. An assertion documents and tests an assumption that should normally be true if the program is correctly implemented. A contract defines obligations between a component and its clients. Defensive programming reduces the consequences of invalid or hostile inputs. Logging records operational evidence for diagnosis and monitoring. Reliable programming therefore requires more than placing try and catch blocks around code. It requires a failure-handling policy: deciding which failures are expected, where they should be handled, what information should be preserved, which resources must be released, and when execution should stop. Learning ObjectivesBy the end of this chapter, you should be able to: - distinguish checked exceptions, unchecked exceptions, and errors;
- explain exception propagation and select suitable handling boundaries;
- design and use custom exception classes;
- apply
try, catch, finally, and try-with-resources correctly; - use assertions for internal invariants and precondition checks during development;
- implement defensive programming and explicit contracts;
- preserve causes and meaningful context when translating exceptions;
- use logging without exposing secrets or producing misleading records;
- evaluate failure-handling policies in a realistic Java application.
1. The Java Failure ModelAn exception is an object representing an abnormal condition that interrupts the normal flow of execution. Java exceptions are instances of classes derived from Throwable. The principal hierarchy is: Throwable: the root of throwable conditions;Exception: conditions that applications may commonly handle;RuntimeException: unchecked exceptions caused frequently by programming errors or invalid API use;Error: serious conditions generally not intended for ordinary application recovery.
Examples of Error include OutOfMemoryError and StackOverflowError. Application code should not normally catch such errors as a general recovery strategy. Catching Throwable is especially dangerous because it also catches errors and can conceal failures that should terminate the operation. A thrown exception transfers control to the nearest compatible handler. If no handler exists in the current method, the exception propagates to the caller. This continues up the call stack until a handler is found or the thread terminates with an uncaught exception. public static int readQuantity(String text) {
return Integer.parseInt(text); // may throw NumberFormatException
}
public static void process(String text) {
int quantity = readQuantity(text);
System.out.println(quantity);
}If text is not a valid integer, readQuantity does not need to catch the exception merely because it can observe it. The exception can propagate to a layer that understands the user interaction, such as a controller or command-line interface. 1.1 Checked and Unchecked ExceptionsJava distinguishes checked and unchecked exceptions primarily through inheritance. A checked exception is an Exception that is not a subclass of RuntimeException. A method that allows a checked exception to escape must either catch it or declare it with throws. Common examples include IOException and SQLException. An unchecked exception is a RuntimeException or an Error. The compiler does not require it to be caught or declared. Examples include NullPointerException, IllegalArgumentException, and IndexOutOfBoundsException. | Category | Typical meaning | Compiler requirement | Examples |
|---|
| Checked exception | Recoverable environmental or external failure | Catch or declare | IOException, SQLException | | Unchecked exception | Programming error or invalid API use | No catch-or-declare requirement | IllegalArgumentException, NullPointerException | | Error | Serious runtime or VM condition | No catch-or-declare requirement | OutOfMemoryError, StackOverflowError |
The distinction is not a guarantee of recoverability. A checked exception is not automatically recoverable, and an unchecked exception is not always impossible to handle. The distinction is a design signal. Checked exceptions make a caller confront a failure possibility at compile time; unchecked exceptions keep the method signature simpler when handling would usually be inappropriate or when the failure indicates a defect. 1.2 Declaring and Catching ExceptionsA method declares checked exceptions in its signature: import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public static String loadText(Path path) throws IOException {
return Files.readString(path);
}A caller can handle the failure: try {
String content = loadText(Path.of("settings.txt"));
System.out.println(content);
} catch (IOException ex) {
System.err.println("Could not read settings: " + ex.getMessage());
}A method may instead propagate it: public static void startApplication() throws IOException {
String configuration = loadText(Path.of("settings.txt"));
// Continue initialization.
}A declaration should describe meaningful behavior, not merely satisfy the compiler. Declaring throws Exception often weakens the API because it gives callers little information about possible failures. 2. Exception Propagation and Handling BoundariesException handling should occur at the lowest level that has enough information to respond correctly, but not necessarily at the point where the exception originates. A file-reading method knows how to close a file, but it may not know whether the user should be asked for another filename. A user-interface layer knows how to display a message, but it should not reconstruct low-level database recovery logic. A useful division is: - Infrastructure layer: detects and reports technical failures.
- Application layer: translates technical failures into domain-relevant failures.
- Boundary layer: communicates with users, APIs, queues, or monitoring systems.
Catching an exception and doing nothing is almost always a defect: try {
save(order);
} catch (Exception ex) {
// Bad: failure is silently discarded.
}If a layer cannot handle an exception, it should generally propagate it, wrap it with useful context, or record and rethrow it. 2.1 Preserving the CauseWhen translating an exception, preserve the original cause using a constructor or initCause: public class OrderStorageException extends RuntimeException {
public OrderStorageException(String message, Throwable cause) {
super(message, cause);
}
}
public void store(Order order) {
try {
repository.insert(order);
} catch (java.sql.SQLException ex) {
throw new OrderStorageException(
"Unable to store order " + order.id(), ex);
}
}The cause chain allows operators and developers to identify both the domain-level meaning and the technical origin. Replacing the original exception with only its message loses stack-trace information and makes diagnosis harder. 2.2 Multi-Catch and OrderingA multi-catch block is useful when several exception types require identical handling: try {
importData(source);
} catch (java.io.IOException | java.text.ParseException ex) {
log.warn("Import failed: {}", ex.getMessage(), ex);
}Catch blocks are examined from top to bottom. A subclass must be caught before its superclass: try {
operation();
} catch (java.io.FileNotFoundException ex) {
// Specific response.
} catch (java.io.IOException ex) {
// General I/O response.
}Catching Exception at an application boundary can be reasonable if the boundary logs the failure, protects the process, and returns a controlled response. It is not a substitute for precise handling inside the application. 3. Custom Exceptions and Domain MeaningA custom exception communicates a failure in the vocabulary of the domain. It should be introduced when callers need to distinguish a condition or when a low-level exception would expose inappropriate implementation details. public final class InsufficientFundsException extends Exception {
private final java.math.BigDecimal requested;
private final java.math.BigDecimal available;
public InsufficientFundsException(
java.math.BigDecimal requested,
java.math.BigDecimal available) {
super("Insufficient funds for requested withdrawal");
this.requested = requested;
this.available = available;
}
public java.math.BigDecimal requested() { return requested; }
public java.math.BigDecimal available() { return available; }
}The exception carries structured data rather than forcing callers to parse a message. A service can use it as follows: public void withdraw(BigDecimal amount)
throws InsufficientFundsException {
if (amount.signum() <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
if (amount.compareTo(balance) > 0) {
throw new InsufficientFundsException(amount, balance);
}
balance = balance.subtract(amount);
}Use a checked custom exception when a reasonable caller is expected to recover or make a deliberate decision. Use an unchecked custom exception when the condition represents invalid use of an API, a violated invariant, or a programming defect that ordinary callers cannot sensibly recover from. Custom exceptions should have: - a precise name;
- a stable semantic meaning;
- constructors that support a message and cause where appropriate;
- relevant structured fields when callers need them;
- no sensitive information in messages;
- documentation describing when they are thrown.
4. Contracts and AssertionsA contract specifies what a method requires, guarantees, or preserves. - Precondition: what must be true before invocation.
- Postcondition: what the method guarantees after successful completion.
- Invariant: a property that remains true for an object or system at defined points.
For a BankAccount, a possible invariant is that the balance is never negative if overdrafts are forbidden. A withdrawal method may require a positive amount and guarantee that the balance is reduced by exactly that amount when it succeeds. Java does not provide a universal built-in contract language, but contracts can be expressed through validation, documentation, types, and assertions. 4.1 Validation Versus AssertionsUse ordinary validation and exceptions for conditions that can arise from external input or normal operation: public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age is outside the valid range");
}
this.age = age;
}Use assertions for internal assumptions that indicate a programming defect: public int removeTop() {
assert size >= 0 : "size invariant violated";
if (size == 0) {
throw new IllegalStateException("Stack is empty");
}
return elements[--size];
}Assertions are disabled by default and may be enabled with the JVM option -ea or --enableassertions. Therefore, assertions must not be used for required input validation, security checks, or business rules. A program must remain correct when assertions are disabled. Avoid side effects in assertion expressions: // Incorrect: behavior changes when assertions are disabled.
assert list.remove(value); Instead, perform the operation separately and assert its result if needed. 5. Resource ManagementResources such as files, sockets, database connections, and locks often require explicit release. Failure to release them can cause leaks, exhaustion, corrupted state, or deadlock. 5.1 finallyA finally block normally executes whether the try block completes normally or abruptly. It is useful for cleanup, but manual cleanup can become complicated when closing itself throws an exception. java.io.InputStream input = null;
try {
input = Files.newInputStream(path);
// Read data.
} finally {
if (input != null) {
input.close();
}
}This pattern is verbose and can obscure the primary failure. 5.2 Try-With-ResourcesA resource that implements AutoCloseable can be managed automatically: try (var input = Files.newInputStream(path)) {
return input.readAllBytes();
} catch (IOException ex) {
throw new DataAccessException("Unable to read " + path, ex);
}The resource is closed at the end of the statement, including when an exception occurs. If both the body and close() throw exceptions, the body exception is primary and the close exception is recorded as a suppressed exception. Multiple resources are closed in reverse declaration order: try (var input = Files.newInputStream(source);
var output = Files.newOutputStream(target)) {
input.transferTo(output);
}The programmer should still consider transactional behavior. Closing a file does not undo partially written data, and closing a database connection does not automatically restore a failed multi-step business operation. Resource safety and transaction consistency are related but distinct concerns. 6. Defensive ProgrammingDefensive programming anticipates invalid states, ambiguous data, environmental failures, and misuse by callers. It is especially important at trust boundaries: web requests, files, command-line arguments, external APIs, and deserialized objects. Effective techniques include: - validate arguments at public boundaries;
- reject invalid values early;
- use immutable objects where practical;
- make invalid states difficult to represent;
- copy mutable input when ownership is unclear;
- limit collection sizes and execution time;
- avoid exposing internal mutable collections;
- distinguish missing, empty, and malformed values;
- fail closed in security-sensitive operations;
- preserve atomicity when updating related data.
Example of defensive copying: public final class Schedule {
private final List<String> meetings;
public Schedule(List<String> meetings) {
Objects.requireNonNull(meetings, "meetings");
this.meetings = List.copyOf(meetings);
}
public List<String> meetings() {
return meetings;
}
}List.copyOf creates an unmodifiable list and prevents later changes to the caller’s list from altering the object’s state. It also rejects null elements, making the invariant explicit.
Defensive programming has costs. Excessive checks can duplicate guarantees already provided by trusted abstractions, increase complexity, or conceal architectural problems. Checks should be placed at meaningful boundaries and should produce clear failures. 7. Logging and ObservabilityLogging is an operational record, not a replacement for error handling. A useful log entry helps answer: what happened, when, where, to which operation, and with what consequence? Typical levels are: ERROR: an operation failed and requires attention;WARN: an unusual condition occurred but execution continued;INFO: significant normal application events;DEBUG: diagnostic details useful during investigation;TRACE: highly detailed execution information.
Use parameterized logging rather than constructing strings unnecessarily: logger.warn("Payment declined for order {}: {}", orderId, reason);
logger.error("Order processing failed for {}", orderId, exception);Important practices include: - log the exception object when a stack trace is needed;
- include correlation or request identifiers in distributed systems;
- avoid passwords, access tokens, payment-card data, and personal data;
- do not log the same exception repeatedly at every layer;
- use stable event names and structured fields where possible;
- align severity with operational impact;
- protect log files through access control and retention policies.
Logging itself can fail or become a performance bottleneck. Applications should use an established logging framework and centralized configuration rather than direct System.out.println calls throughout production code. 8. Failure-Handling PoliciesA reliable system explicitly defines what happens after failure. Common policies include: Fail fastStop immediately when continuing could produce invalid results. This is appropriate for violated invariants, invalid configuration, and security failures. RetryAttempt an operation again when failure is likely transient, such as a temporary network timeout. Retries require limits, delays, and awareness of idempotency. Retrying a non-idempotent payment request can create duplicate charges unless the protocol provides an idempotency key. FallbackUse an alternative source or behavior, such as cached data, when the primary service is unavailable. The fallback must not silently produce dangerously stale or incorrect results. CompensateUndo or counteract a completed step when a later step fails. Compensation is common in distributed workflows, but it is not always equivalent to a database rollback. Reject and reportReturn a controlled error to the caller, record sufficient diagnostic information, and preserve system integrity. A policy should answer four questions: - Is the failure expected or evidence of a defect?
- Is recovery safe and technically possible?
- Who has enough context to decide what to do?
- What information must be retained for diagnosis and audit?
9. Worked Example: A Reliable Configuration LoaderConsider a configuration loader that reads a required properties file. The design must distinguish a missing file, malformed values, and an internal coding defect. public final class ConfigurationException extends Exception {
public ConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}
public final class AppConfig {
private final URI serviceUri;
private final Duration timeout;
public AppConfig(URI serviceUri, Duration timeout) {
this.serviceUri = Objects.requireNonNull(serviceUri);
this.timeout = Objects.requireNonNull(timeout);
if (!serviceUri.isAbsolute()) {
throw new IllegalArgumentException("Service URI must be absolute");
}
if (timeout.isNegative() || timeout.isZero()) {
throw new IllegalArgumentException("Timeout must be positive");
}
}
public static AppConfig load(Path path) throws ConfigurationException {
Properties properties = new Properties();
try (InputStream input = Files.newInputStream(path)) {
properties.load(input);
URI uri = URI.create(required(properties, "service.uri"));
Duration timeout = Duration.ofMillis(
Long.parseLong(required(properties, "timeout.ms")));
return new AppConfig(uri, timeout);
} catch (NoSuchFileException ex) {
throw new ConfigurationException(
"Configuration file does not exist: " + path, ex);
} catch (IOException | IllegalArgumentException ex) {
throw new ConfigurationException(
"Configuration is unreadable or invalid: " + path, ex);
}
}
private static String required(Properties p, String key) {
String value = p.getProperty(key);
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Missing property: " + key);
}
return value.trim();
}
}This example demonstrates several decisions: - file closure is guaranteed by try-with-resources;
- missing files are distinguished from other failures;
- malformed values are translated into a domain-level checked exception;
- the original cause is preserved;
- the constructed configuration validates its own invariants;
- invalid configuration cannot silently become a partially initialized object.
At the application boundary, the exception could be logged once and cause startup to stop. Continuing with an unknown service URI or timeout would be less safe than failing fast. 10. Applied Case: Order Processing ServiceBackgroundConsider an illustrative online retailer. An order service validates an order, reserves inventory, charges a payment method, and publishes a shipping event. ProblemSeveral failures are possible: invalid quantities, unavailable inventory, payment timeouts, duplicate requests, and message-broker outages. A simplistic method that catches every exception and returns false would hide the cause and might leave inventory reserved without payment or payment completed without a shipping event. AnalysisThe service classifies failures by recoverability and consistency risk: | Failure | Classification | Policy |
|---|
| Negative quantity | Invalid input | Reject immediately | | Product unavailable | Expected business outcome | Return a clear domain response | | Payment timeout | Potentially transient | Bounded retry if idempotent; otherwise investigate status | | Database connection loss | Technical failure | Roll back local transaction and report | | Event publication failure | Integration failure | Use an outbox or durable retry mechanism |
Approach and SolutionThe service validates the request at its boundary. It uses a transaction for local order and inventory state. Payment requests include an idempotency key derived from the order identifier. Instead of publishing directly inside the database transaction, the service writes an outbox record in the same transaction. A separate publisher delivers the event and retries safely. Exceptions are translated at meaningful boundaries. A payment client may throw PaymentTimeoutException; the order service records an intermediate status such as PAYMENT_PENDING rather than claiming success. A controller maps expected domain exceptions to suitable response codes and maps unexpected failures to a generic error while logging the detailed cause internally. OutcomeThe design avoids silent failure, reduces duplicate payment risk, and permits operational staff to investigate pending orders. It also recognizes that exception handling alone cannot provide distributed transactionality. Idempotency, durable messaging, transaction boundaries, and monitoring are required as complementary reliability mechanisms. Learning Points- Failure handling is a system design concern, not just a syntax concern.
- An exception should communicate a meaningful condition to its intended caller.
- Retrying is safe only when the operation and protocol support it.
- Local rollback cannot automatically undo remote side effects.
- Logs, status models, and durable records may be necessary when a failure leaves the outcome uncertain.
11. Common Errors and LimitationsSwallowing exceptionsAn empty catch block destroys evidence and may allow corrupted state to continue. Catching overly broad typesCatching Exception deep inside a library can hide programming errors and make recovery inappropriate. Logging and rethrowing indiscriminatelyIf every layer logs the same stack trace, one failure produces noisy duplicate records. Usually the layer that can add meaningful context or make the final response should log at high severity. Returning null for failurenull often loses the reason for failure and creates later NullPointerException failures. Use a domain result, Optional for genuine absence, or an exception according to the API’s semantics.
Using assertions for external validationAssertions may be disabled, so they cannot enforce security or business requirements. Exposing implementation detailsPublic APIs should not force callers to understand vendor-specific database or networking exceptions unless that dependency is intentional. Assuming cleanup means rollbackClosing resources releases them; it does not necessarily restore application state. 12. Contemporary Practice and Professional RelevanceModern Java systems commonly combine exceptions with structured logging, metrics, tracing, validation libraries, static analysis, and automated tests. Static-analysis tools can identify unreachable catches, ignored return values, resource leaks, and suspicious exception handling. Testing frameworks support tests for expected exceptions, failure paths, and resource cleanup. In cloud and distributed systems, reliability also depends on timeouts, cancellation, circuit breakers, bulkheads, rate limits, health checks, and idempotent interfaces. These mechanisms do not replace Java exceptions; they determine how exceptions are interpreted at system boundaries. AI-assisted coding tools can generate plausible try/catch blocks, but generated code must be reviewed for swallowed exceptions, unsafe retries, leaked resources, sensitive logging, and incorrect assumptions about transactional behavior. Reliability remains a design and verification responsibility. Best-Practice Checklist- Validate untrusted input at a clear boundary.
- Use specific exception types with stable meanings.
- Catch only when you can handle, translate, compensate, or report appropriately.
- Preserve the original cause when wrapping exceptions.
- Prefer try-with-resources for
AutoCloseable resources. - Use assertions for internal assumptions, not required validation.
- Avoid sensitive data in exception messages and logs.
- Define retry limits and verify idempotency before retrying.
- Separate technical failures from domain outcomes.
- Test both successful paths and failure paths.
- Document failure behavior in public APIs.
Chapter SummaryJava reliability mechanisms express and manage abnormal conditions at different levels. Checked exceptions make selected failure possibilities explicit in method contracts, while unchecked exceptions commonly represent invalid API use or programming defects. Exceptions propagate through callers until a compatible handler is found, so handling should occur where sufficient context exists. Custom exceptions provide domain meaning and can preserve structured information. Exception translation should retain the original cause. Assertions are useful for internal invariants but are optional at runtime and must not replace validation of external input. Try-with-resources is the preferred mechanism for deterministic release of closeable resources. Defensive programming validates boundaries, protects object invariants, limits unsafe assumptions, and prevents invalid states. Logging supports diagnosis and operations but must avoid secrets, duplication, and misleading severity. Finally, robust systems use explicit policies such as fail-fast behavior, bounded retries, fallbacks, compensation, idempotency, and durable messaging. Reliable programming is therefore a combination of language features, API design, operational practice, and system architecture. Key TermsException: An object representing an abnormal condition that interrupts normal execution. Checked exception: A non-RuntimeException exception that must be caught or declared. Unchecked exception: A RuntimeException or Error that does not require catch-or-declare treatment. Error: A serious condition generally outside ordinary application recovery. Propagation: Movement of an uncaught exception up the call stack. Exception handler: Code that responds to a compatible thrown exception. Cause chain: The linked sequence of underlying exceptions retained during translation. Custom exception: An application-defined exception expressing a specific domain or technical condition. Contract: A specification of preconditions, postconditions, and invariants. Precondition: A condition required before an operation begins. Postcondition: A guarantee made after successful completion. Invariant: A property that must remain true at defined points in execution. Assertion: A development-time check of an internal assumption. Try-with-resources: A Java construct that automatically closes AutoCloseable resources. Defensive programming: Designing software to anticipate invalid inputs, misuse, and environmental failure. Idempotency: A property whereby repeating an operation has the same intended effect as performing it once. Suppressed exception: An exception recorded by the runtime when cleanup fails while another exception is already primary. Structured logging: Logging event data in consistent fields suitable for searching and analysis. Failure-handling policy: A defined decision about detection, recovery, reporting, and termination after failure. Review QuestionsShort Answer Questions- What is the difference between a checked and an unchecked exception in Java?
- Why should a wrapped exception normally preserve its original cause?
- Why are assertions unsuitable for validating user input?
- What problem does try-with-resources solve?
- State two types of information that should not be placed in production logs.
Long Answer Questions- Explain exception propagation and discuss how a programmer should choose an appropriate handling boundary.
- Compare validation, assertions, and custom exceptions as mechanisms for expressing contracts.
- Describe how try-with-resources handles exceptions thrown both by the resource body and by
close(). - Explain the design considerations involved in creating a checked custom exception.
- Discuss why retrying a failed remote operation can be unsafe and how idempotency changes the analysis.
Analytical / Application Questions- Design a failure-handling policy for a file-import service that encounters missing files, malformed records, temporary network failures, and duplicate submissions.
- Analyze an order service that charges a customer successfully but fails while publishing a shipping event. Propose a design that avoids falsely reporting completion and supports recovery.
- Review a method that catches
Exception, logs only ex.getMessage(), returns null, and manually closes a stream in several branches. Identify the reliability problems and propose a redesigned approach.
Multiple Choice Questions with AnswersQuestion 1Which statement about a checked exception is correct? A. It must always be handled by an assertion. B. It must be caught or declared by a method that allows it to escape. C. It is always caused by invalid user input. D. It cannot be wrapped in another exception. Correct Answer: B. It must be caught or declared by a method that allows it to escape. Question 2What happens when an exception is not handled in the method where it is thrown? A. It is automatically converted to null. B. It is ignored after the method returns. C. It propagates up the call stack. D. It is always converted into an Error. Correct Answer: C. It propagates up the call stack. Question 3Which mechanism is preferred for closing an AutoCloseable resource? A. A busy-wait loop B. An assertion C. Try-with-resources D. Returning null Correct Answer: C. Try-with-resources Question 4Why should an exception wrapper preserve the original cause? A. To disable stack traces B. To retain diagnostic information about the underlying failure C. To make every exception unchecked D. To prevent propagation Correct Answer: B. To retain diagnostic information about the underlying failure Question 5Which condition is most appropriate for an assertion? A. A password supplied by a user is too short B. A required HTTP request field is missing C. An internal data-structure invariant has been violated D. A payment provider is temporarily unavailable Correct Answer: C. An internal data-structure invariant has been violated Question 6What is a major risk of retrying a non-idempotent payment operation? A. The operation becomes compile-time checked B. The payment may be processed more than once C. The resource is automatically closed D. The exception cause is removed Correct Answer: B. The payment may be processed more than once Question 7Which logging practice is most appropriate? A. Log passwords to support debugging B. Log every exception at every layer C. Use structured context and protect sensitive information D. Replace all exception objects with their messages Correct Answer: C. Use structured context and protect sensitive information Question 8Which exception is most likely appropriate for a caller that can reasonably respond to insufficient account funds? A. A domain-specific exception describing insufficient funds B. StackOverflowError C. AssertionError only D. NullPointerException Correct Answer: A. A domain-specific exception describing insufficient funds Question 9What occurs if both the try-with-resources body and resource closing throw exceptions? A. Both are discarded B. The close exception always replaces the body exception C. The body exception is primary and the close exception is suppressed D. The program must continue normally Correct Answer: C. The body exception is primary and the close exception is suppressed Question 10Which policy is most appropriate when configuration is invalid and continuing could produce unsafe behavior? A. Fail fast B. Retry forever C. Silently use arbitrary defaults D. Ignore the configuration error Correct Answer: A. Fail fast
|