Java Exceptions

Java Exceptions

Java Exceptions Tutorial 

Exceptions are a way of describing exceptional circumstances within a program. They are an indicator that something unexpected (exceptional) has occurred. For that reason, exceptions are efficient at interrupting the current flow of the program and signaling that there is something that requires attention.

 

As such, programs that utilize exceptions judiciously benefit from a better control flow and become more robust and informative for the user. Even so, using exceptions indiscriminately can cause performance degradation.

 

Within Java, exceptions can be thrown or caught. Throwing an exception involves indicating to the code that an exception has been encountered, using the throw keyword to signal the JVM to find any code capable of handling this exceptional circumstance within the current stack.

 

Catching an exception involves telling the compiler which exceptions can be handled, and which part of the code should be monitored for these exceptions to occur. This is denoted within the try/catch Java block

 

All exceptions inherit from Throwable. Classes that are inherited from Throwable can be defined in the catch clause of a try/catch statement. The Error classes are primarily used by the JVM to denote serious and/or fatal errors. According to the Java documentation, applications are not expected to catch Error exceptions since they are considered fatal (think of a computer being on fire). The bulk of exceptions within a Java program will be inherited from the Exception class.

 

Within the JVM there are two types of exceptions: checked and unchecked. Checked exceptions are enforced by methods. In the method signature, you can specify the kind of exceptions a method can throw. This requires any caller of the method to create a try/catch block, which handles the exceptions that were declared within the method signature.

 

Unchecked exceptions do not require such a stringent convention, and are free to be thrown anywhere without enforcing the implementation of a try/catch block. Even so, unchecked exceptions are usually discouraged because they can lead to threads unraveling (if nothing catches the exception) and poor visibility of problems.

 

Exception classes that inherit from RuntimeException are considered to be unchecked exceptions, whereas exception classes that inherit directly from Exception are considered to be checked exceptions.

 

Be aware that the act of throwing exceptions is expensive (compared with other language construct alternatives), and as such throwing exceptions makes a poor substitute for control flow. For example, you shouldn’t throw an exception to indicate an expected result of a method call (say a method like isUsernameValid (String username).

 

It is a better practice to call the method and return a boolean with the result than try to throw an InvalidUsernameException to indicate failure.

 

While exceptions play an essential role in solid software development, logging of exceptions can be just as important. Logging within an application helps the developer to understand what events are occurring without the need for debugging the code. This is especially true in production environments where there isn’t the opportunity for live debugging.

 

Logging for Java is very mature. There are many open source projects that are widely accepted as the de facto standard for logging. In the recipes in this blog, you will use Java’s Logging framework and the Simple Logging Façade for Java (SLF4J). Both of these projects together create a good-enough solution for most logging needs.

 

For the recipes involving SLF4J and Log4j, download SLF4J (http://www.slf4j.org/) and put it in your project’s dependency path. This blog will also touch upon the lower-level JVM logging that has been added with the release of Java 9.

 

Catching Exceptions

Catching Exceptions

Problem You want to gracefully handle any exceptions generated from your code.

Solution

Use the built-in try/catch language construct to catch exceptions. Do so by wrapping any blocks of code that may throw an exception within a try/catch block. In the following example, a method is used to generate a Boolean value to indicate whether a specified String is greater than five characters long. If the String that’s passed as an argument is null, a NullPointerException is thrown by the length() method and caught within the catch block.

private void start() {
System.out.println("Is th String 1234 longer than 5 chars?:"+ isStringShorterThanFiveCharacters("1234"));
System.out.println("Is th String 12345 longer than 5 chars?:"+ isStringShorterThanFiveCharacters("12345"));
System.out.println("Is th String 123456 longer than 5 chars?:"+ isStringShorterThanFiveCharacters("123456"));
System.out.println("Is th String null longer than 5 chars?:"+ isStringShorterThanFiveCharacters(null));
}
private boolean isStringShorterThanFiveCharacters(String aString) { try {
return aString.length() > 5;
} catch (NullPointerException e) { System.out.println("An Exception Occurred: " + e); return false;
}
}

 

How It Works

exception

The try keyword specifies that the enclosed code segment have the potential to raise an exception. The catch clause is placed at the end of the try clause. Each catch clause specifies which exception is being caught. If a catch clause is not provided for a checked exception, the compiler will generate an error.

 

Two possible solutions are to add a catch clause or to include the exception in the throws clause of the enclosing method. Any checked exceptions that are thrown but not caught will propagate up the call stack. If this method doesn’t catch the exception, the thread that executed the code terminates. If the thread terminating is the only thread in the program, it terminates the execution of the program.

 

If a try clause needs to catch more than one exception, more than one exception can be specified, separated by a bar character. For instance, the following try/catch block could be used for catching both a NumberFormatException and a NullPointerException.

try {

code here

} catch (NumberFormatException|NullPointerException ex) {

logging

}

Note  Be careful when throwing an exception. If the thrown exception is not caught, it will propagate up the call stack; and if there isn’t any catch clause capable of handling the exception, it will cause the running thread to terminate (also known as unraveling). If your program has only one main thread, an uncaught exception will terminate your program.

 

Guaranteeing a Block of Code Is Executed

Block of Code Is Executed

Problem

You want to write code that executes when control leaves a code segment, even if control leaves due to an error being thrown or the segment ending abnormally. For example, you have acquired a lock and want to be sure that you are releasing it correctly. You want to release the lock in the event of an error and also in the event of no error.

 

Solution

Use a try/catch/finally block to properly release locks and other resources that you acquire in a code segment. Place the code that you want to have executed regardless of exceptions into the finally clause. In the example, the finally keyword specifies a code block that will always execute, regardless of whether an exception was thrown in the try block. Within the finally block, the lock is released by calling lock.unlock():

private void callFunctionThatHoldsLock() {
myLock.lock();
try {
int number = random.nextInt(5);
int result = 100 / number;
System.out.println("A result is " + result);
FileOutputStream file = new FileOutputStream("file.out");
file.write(result);
file.close();
} catch (FileNotFoundException e) { e.printStackTrace();
} catch (IOException e) { e.printStackTrace();
} catch (Exception e) { e.printStackTrace();
} finally {
myLock.unlock();
}
}

 

How It Works

try/catch/finally block

Code that is placed within the finally clause of a try/catch/finally block will always be executed. In this example, by acquiring the lock at the beginning of the function and then releasing it in the finally block, you guarantee that the lock will be released at the end of the function regardless of whether an exception (checked or unchecked) is thrown. In all, acquired locks should always be released in a finally block.

 

In the example, suppose that the mylock.unlock() function call were not in the finally block (but at the end of the try block); if an exception were to happen in this case, the call to mylock.unlock() would not happen because code execution would be interrupted in the location where the exception happened. In that case, the lock would be forever acquired and never released.

 

Caution  If you need to return a value on a method, be very careful of returning values in the finally block. A return statement in the finally block will always execute, regardless of any other return statements that might have happened within the try block.

 

Throwing Exceptions

Throwing Exceptions

Problem

You want to abort the execution of the current code path by throwing an exception if a certain situation occurs within your application.

Solution

Use the throw keyword to throw a specified exception when the situation occurs. Using the throw keyword, you can signal the current thread to look for try/catch blocks (at the current level and up the stack), which can process the thrown exception. In the following example, the callSomeMethodThatMightThrow throws a NullPointerException if the parameter passed in is null.

private void start() {
try {
callSomeMethodThatMightThrow(null);
} catch (IllegalArgumentException e) {
System.out.println("There was an illegal argument exception!");
}
}
private void callSomeFunctionThatMightThrow(Object o) {
if (o == null) throw new NullPointerException("The object is null");
}

In this code example, the method callSomeMethodThatMightThrow checks to ensure that a valid argument was passed to it. If the argument is null, it then throws a NullPointerException, signaling that the caller of this method invoked it with the wrong parameters.

 

How It Works

The throw keyword allows you to explicitly generate an exceptional condition. When the current thread throws an exception, it doesn’t execute anything beyond the throw statement and instead transfers control to the catch clause (if there are any) or terminates the thread.

 

Note When throwing an exception, be sure that you intend to do so. If an exception is not caught as it propagates up the stack, it will terminate the thread that is executing (also known as unraveling). If your program has only one main thread, an uncaught exception will terminate your program.

 

Catching Multiple Exceptions

Problem

A block of code in your application has the possibility of throwing multiple exceptions. You want to catch each of the exceptions that may occur within a try block.

 

Solution 1

More than one catch clause can be specified in situations where multiple exceptions may be encountered within the same block. Each catch clause can specify a different exception to handle, so that each exception can be handled in a different manner. In the following code, two catch clauses are used to handle an IOException and a ClassNotFoundException.

try {
Class<?> stringClass = Class.forName("java.lang.String");
FileInputStream in = new FileInputStream("myFile.log") ; // Can throw IOException in.read();
} catch (IOException e) {
System.out.println("There was an exception "+e);
} catch (ClassNotFoundException e) { System.out.println("There was an exception "+e);
}

 

Solution 2

If your application has the tendency to throw multiple exceptions within a single block, then a vertical bar operator (|) can be utilized for handling each of the exceptions in the same manner. In the following example, the catch clause specifies multiple exception types separated with a vertical bar (|) to handle each of the exceptions in the same manner.

try {
Class<?> stringClass = Class.forName("java.lang.String"); FileInputStream in = new FileInputStream("myFile.log") ;
Can throw IOException in.read();
} catch (IOException | ClassNotFoundException e) {
System.out.println("An exception of type "+e.getClass()+" was thrown! "+e);
}

 

How It Works

There are a couple of different ways to handle situations where multiple exceptions may be thrown. You can specify separate catch clauses to handle each of the exceptions in a different way. To handle each of the exceptions in the same manner, you can utilize a single catch clause and specify each exception separated with a vertical bar operator.

 

Note If you’re catching an exception in multiple catch blocks (Solution 1), make sure that the catch blocks are defined from the most specific to the most general. Failure to follow this convention will prevent an exception from being handled by the more specific blocks. This is most important when there are catch (Exception e) blocks, which catch almost all exceptions.

 

Having a catch (Exception e) block—called a catch-all or Pokémon exception handler (gotta catch them all)—is usually poor practice because such a block will catch every exception type and treat them all the same. This becomes a problem because the block can catch other exceptions that may occur deeper within the call stack that you may not have intended the block to catch (an OutOfMemoryException).

It is a best practice to specify each possible exception, rather than specifying a catch-all exception handler to catch all exceptions.

 

Catching the Uncaught Exceptions

Problem You want to know when a thread is being terminated due to an uncaught exception such as a NullPointerException.

Solution 1

When creating a Java thread, sometimes you need to ensure that any exception is caught and handled properly to help determine the reason for the thread termination. To that effect, Java allows you to register an ExceptionHandler() either per thread or globally. The following code demonstrates an example of registering an exception handler on a per-thread basis.

private void start() {
Thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> {
System.out.println("Woa! there was an exception thrown somewhere! "+t.getName()+": "+e);
});
final Random random = new Random();
for (int j = 0; j < 10; j++) {
int divisor = random.nextInt(4);
System.out.println("200 / " + divisor + " Is " + (200 / divisor));
}
}

The for loop in this thread will execute properly until an exception is encountered, at which time the DefaultUncaughtExceptionHandler will be invoked. UncaughtExceptionHandler is a functional interface, so it is possible to utilize a lambda expression to implement the exception handler.

 

Solution 2

It is possible to register an UncaughtExceptionHandler on a specific thread. After doing so, any exception that occurs within the thread and that has not been caught will be handled by the uncaughtException() method of the UncaughtExceptionHandler(). For example:

private void startForCurrentThread() {
Thread.currentThread().setUncaughtExceptionHandler((Thread t, Throwable e) -> {
System.out.println("In this thread "+t.getName()+" an exception was thrown "+e);
});
Thread someThread = new Thread(() -> {
System.out.println(200/0);
});
someThread.setName("Some Unlucky Thread");
someThread.start();
System.out.println("In the main thread "+ (200/0));
}

In the previous code, an UncaughtExceptionHandler is registered on the currentThread. Just like Solution 1, UncaughtExceptionHandler is a functional interface, so it is possible to utilize a lambda expression to implement the exception handler.

 

How It Works

The Thread.defaultUncaughtExceptionHandler() will be invoked for each unchecked exception that has not been caught. When the UncaughtExceptionHandler() handles an exception, it means that there was no try/catch block in place to catch the exception. As such, the exception bubbled all the way up the thread stack.

 

This is the last code executed on that thread before it terminates. When an exception is caught on either the thread’s or the default’s UncaughtExceptionHandler(), the thread will terminate. The UncaughtExceptionHandler() can be used to log information on the exception to help pinpoint the reason of the exception.

 

In the second solution, the UncaughtExceptionHandler() is set up specifically for the current thread. When the thread throws an exception that is not caught, it will bubble up to the UncaughtExceptionHandler() of the thread. If this is not present, it will bubble up to the defaultUncaughtExceptionHandler(). Again, in either situation, the thread originating the exception will terminate.

 

Tip When dealing with multiple threads, it is always good practice to explicitly name the threads. It makes life easier to know exactly which thread caused the exception, rather than having to trace down an unknown thread named like Thread-## (the default naming pattern of unnamed threads).

 

Managing Resources with try/catch Blocks

Problem In the event of an exception, you need to ensure that any resources used within a try/catch block are released.

Solution

Make use of the Automatic Resource Management (ARM) feature, which can be specified with a try-with-resources statement. When using a try-with-resources statement, any resources that are specified within the try clause are automatically released when the block terminates. In the following code, the FileOutputStream, BufferedOutputStream, and DataOutputStream resources are automatically handled by the try-with-resources block.

try (
FileOutputStream fos = new FileOutputStream("out.log"); BufferedOutputStream bos = new BufferedOutputStream(fos); DataOutputStream dos = new DataOutputStream(bos)
) {
dos.writeUTF("This is being written");
} catch (Exception e) {
System.out.println("Some bad exception happened ");
}

 

How It Works

In most cases, you want to cleanly close/dispose of resources that are acquired within a try/catch block after the block execution is complete. If a program does not close/dispose of its resources or does so improperly, the resources could be acquired indefinitely, causing issues such as memory leaks to occur. Most resources are limited (file handles or database connections), and as such will cause performance degradation (and more exceptions to be thrown).

 

To avoid these situations, Java provides a means of automatically releasing resources when an exception occurs within a try/catch block. By declaring a try-with-resources block, the resource on which the try block was checked will be closed if there is an exception thrown within the block.

 

Most of the resources that are built into Java will work properly within a try-with-resources statement (for a full list, see implementers of the java.lang.AutoCloseable interface). Also, third-party implementers can create resources that will work with the try-with-resources statements by implementing the AutoCloseable interface.

 

The syntax for the try-with-resources statement involves the try keyword, followed by an opening parenthesis and then followed by all the resource declarations that you want to have released in the event of an exception or when the block completes, and ending with a closing parenthesis.

 

Note that if you try to declare a resource/variable that doesn’t implement the AutoCloseable interface, you will receive a compiler error. After the closing parenthesis, the syntax of the try/catch block is the same as a normal block.

 

The main advantage of the try-with-resources feature is that it allows a cleaner release of resources. Usually when acquiring a resource, there are a lot of interdependencies (creating file handlers, which are wrapped in output streams, which are wrapped in buffered streams).

 

Properly closing and disposing of these resources in exceptional conditions requires checking the status of each dependent resource and carefully disposing of it, and doing so requires that you write a lot of code. By contrast, the try-with-resources construct allows the JVM to take care of proper disposal of resources, even in exceptional conditions.

 

Note A try-with-resources block will always close the defined resources, even if no exceptions were thrown.

 

Creating an Exception Class

Problem You want to create a new type of exception that can be used to indicate a particular event.

Solution 1

Create a class that extends java.lang.RuntimeException to create an exception class that can be thrown at any time. In the following code, a class identified by IllegalChatServerException extends RuntimeException and accepts a String as an argument to the constructor. The exception is then thrown when a specified event occurs within the code.

class IllegalChatServerException extends RuntimeException { IllegalChatServerException(String message) {
super(message);
}
}
private void disconnectChatServer(Object chatServer) {
if (chatServer == null) throw new IllegalChatServerException("Chat server is empty");
}
Solution 2
Create a class that extends java.lang.Exception to generate a checked exception class. A checked exception is required to be caught or rethrown up the stack. In the following example, a class identified as ConnectionUnavailableException extends java.lang.Exception and accepts a String as an argument to the constructor. The checked exception is then thrown by a method in the code.
class ConnectionUnavailableException extends Exception {
ConnectionUnavailableException(String message) { super(message);
}
}
private void sendChat(String chatMessage) throws ConnectionUnavailableException { if (chatServer == null)
throw new ConnectionUnavailableException("Can't find the chat server");
}

 

How It Works

Sometimes there is a requirement to create a custom exception, especially in situations when you’re creating an API. The usual recommendation is to use one of the available Exception classes provided by the JDK. For example, use IOException for I/O-related issues or the IllegalArgumentException for illegal parameters. If there isn’t a JDK exception that fits cleanly, you can always extend java.lang.Exception or java.lang.

 

RuntimeException and implement its own family of exceptions.

Depending on the base class, creating an Exception class is fairly straightforward. Extending RuntimeException allows you the ability to throw the resulting exception any time without requiring it to be caught up the stack. This is advantageous in that RuntimeException is a more lax contract to work with, but throwing such an exception can lead to thread termination if the exception is not caught.

 

Extending Exception instead allows you to clearly force any code that throws the exception to be able to handle it within a catch clause. The checked exception is then forced by contract to implement a catch handler, potentially avoiding a thread termination.

 

In practice, we discourage extending RuntimeException because it can lead to poor exception handling. Our rule of thumb is that if it’s possible to recover from an exception, you should create the associated exception class by extending Exception. If a developer cannot reasonably be expected to recover from the exception (say a NullPointerException), then extend RuntimeException.

 

Rethrowing the Caught Exception

Problem Your application contains a multicatch exception, and you want to rethrow an exception that was previously caught.

Solution

Throw the exception from a catch block, and it will rethrow it on the same type as it was caught. In the following example, exceptions are caught within a block of code and rethrown to the method’s caller.

private void doSomeWork() throws IOException, InterruptedException { LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
try {
FileOutputStream fos = new FileOutputStream("out.log"); DataOutputStream dos = new DataOutputStream(fos); while (!queue.isEmpty()) {
dos.writeUTF(queue.take());
}
} catch (InterruptedException | IOException e ) { e.printStackTrace();
throw e;
}
}

 

How It Works

It is possible to simply throw the exception that has been previously caught, and the JVM will bubble the exception to the appropriate type. As is the case of throwing a checked exception; it must also be defined in the method declaration. In the example to this solution, the doSomeWork() method throws an IOException and an InterruptedException, which causes the calling code to perform a try-catch to handle the thrown exception appropriately.

 

Logging Events Within Your Application

Problem You want to log events, debug messages, error conditions, and other events within your application.

Solution

Utilize SLF4J within your application, along with the Java Logging API, to implement a logging solution. The following example first creates a logger object with the name of recipeLogger. In this example, the SLF4J API is used to log an informational message, a warning message, and an error message:

private void loadLoggingConfiguration() {
FileInputStream ins = null;
try {
ins = new FileInputStream(new File("logging.properties")); LogManager.getLogManager().readConfiguration(ins);
} catch (IOException e) { e.printStackTrace();
}
}
private void start() {
loadLoggingConfiguration();
Logger logger = LoggerFactory.getLogger("recipeLogger"); logger.info("Logging for the first Time!"); logger.warn("A warning to be had"); logger.error("This is an error!");
}

 

How It Works

In the example, loadLogConfiguration() function opens a stream to the logging.properties file and passes it to java.util.logging.LogManager(). Doing so configures the java.util.logging framework to use the settings specified in the logging.properties file. Then, within the start method of the solution, the code acquires a logger object named recipeLogger. The example proceeds to log messages to through recipeLogger.

 

SLF4J provides a common API using a simple facade pattern that abstracts the underlying logging implementation. SLF4J can be used with most of the common logging frameworks, such as the Java Logging API (java.util.logging), Log4j, Jakarta Commons Logging, and others. In practice, SLF4J provides the flexibility to choose (and swap) logging frameworks and allows projects that use SLF4J to quickly become integrated into an application’s selected logging framework.

 

To use SLF4J in an application, download the SLF4J binaries located at SLF4J. Once they’re downloaded, extract the contents and add slf4j-api-x.x.x.jar to the project. This is the main .jar file that contains the SLF4J API (on which a program can call to log information). After adding the slf4j-api-x.x.x.jar file to the project, find slf4j-jdk14-x.x.x.jar and add that to the project. This second file indicates that SLF4J will use the java.util.logging classes to log information.

 

The way SLF4J works is that at runtime SLF4J scans the class path and picks the first .jar that implements the SLF4J API. In the example case, the slf4j-jdk14-x.x.x.jar is found and loaded. This .jar represents the native Java Logging Framework (known as jdk.1.4 logging).

 

If, for example, you wanted to use another logging framework, replace slf4j-jdk14-x.x.x.jar with the corresponding SLF4J implementation for the desired logger. For example, to use Apache’s Log4j logging framework, include slf4j-log4j12-x.x.x.jar.

 

Note The java.util.logging framework is configured by the properties log file.

Once SLF4J is configured, you can log information in your application by calling the SLF4J logging methods. The methods log information depending on the logging level. The logging level can then be used to filter which messages are actually logged. The ability to filter messages by log level is useful because there may be a lot of informational or debugging information being logged.

 

If there is the need to troubleshoot an application, the logging level can be changed, and more information can be made visible in the logs without changing any code. The ability to filter messages through their level is referred to as setting the log level.

 

Each logging framework reference contains its own configuration file that sets the log level (among other things, such as the logging file name and logging-file configurations). In the example case, because SLF4J is using the java.util.logging framework to log, you would need to configure the java.util.logging properties for the desired logging. 

 

Table  Logging Levels

Trace Least important of the logging events
Debug Use for extra information that helps with debugging
Info Use for everyday logging messages
Warn Use for recoverable issues, or where the suspicions of a wrong setting/nonstandard
behavior happens
Error Use for exceptions, actual errors, and things that you really need to know
Fatal Most important

 

Note  When setting the log level, loggers will log at that level and below. Therefore, if a logging configuration sets the log level to info, messages at the Info, Warn, Error, and Fatal levels will be logged.

 

Rotating and Purging Logs

Problem

You have started to log information, but the information logged continues growing out of control. You would like to keep only the last 250KB worth of log entries within your log files.

Solution

Use SLF4J with java.util.logging to configure rolling logs. In this example, a logger named recipeLogger is used to log many messages. The output will produce rolled log files with the most recent logged information in the important Log0.log file.

loadLoggingConfiguration();
Logger logger = LoggerFactory.getLogger("recipeLogger"); logger.info("Logging for the first Time!"); logger.warn("A warning to be had"); logger.error("This is an error!");
Logger rollingLogger = LoggerFactory.getLogger("rollingLogger"); for (int i =0;i < 5000;i++) {
rollingLogger.info("Logging for an event with :"+i);
}
logging.properties file
handlers = java.util.logging.FileHandler
recipeLogger.level=INFO
.level=ALL
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.FileHandler.pattern=ImportantApplication%d.log java.util.logging.FileHandler.limit=50000 java.util.logging.FileHandler.count=4

 

How It Works

To control the size of log files, configure the java.util.logging framework and specify rolling log files. Choosing the rolling log files option causes the latest information to be kept in ImportantApplication0.log. Progressively older information will be in ImportantApplication1.log, ImportantApplication2.log, and so forth.

 

When ImportantApplication0.log fills to the limit you specify (50,000 bytes in this example), its name will be rotated to ImportantApplicationLog1.log, and the other files will have their names similarly rotated downward. The number of log files to maintain is determined by the java.util.logging. FileHandler.count property, which is set to 4 in this recipe’s example.

 

The logging.properties file begins by defining the handlers that the java.util.logging framework will use. Handlers are objects that take care of logging messages. FileHandler is specified in the recipe, which logs messages to files.

 

Other possible handlers are the ConsoleHandler (logs to the system.output device), SocketHandler (logs to a socket), and MemoryHandler (keeps logs in a circular buffer in memory). There is also the possibility of specifying your own handler implementation by creating a class that extends the Handler abstract class.

 

Next, the logging levels are defined. Within a logging framework there is the concept of separate logger objects. A logger can carry different configurations (for example, different logging levels) and can be identified in the log file. The example configures the recipeLogger’s level to info, whereas the root logger’s level is ALL (root loggers in the java.util.logging framework are denoted by not having any prefix before the property).

 

The next section of the logging.properties file defines the FileHandler configuration. The formatter indicates how the log information will be written to disk. The simpleFormatter writes the information as plain text, with a line indicating the date and time, a line with the logging level, and the message to be logged.

 

The other default choice for the formatter is XMLFormatter, which will create XML markup containing the date, time, logger name, level, thread, and message information for each log event. You can create custom formatters by extending the Formatter abstract class.

 

Following the formatter, the fileHandler pattern is defined. This specifies the file name and location of the log files (the %d is replaced by the rolling log number [0 ~ 4]). The Limit property defines how many bytes the log can have before rolling over (50,000 bytes ~ 50kb). The count defines the maximum index of log files to keep (in this recipe’s case, it’s 4).

 

Note Logging can be expensive; if you are logging a lot of information, your Java program will start consuming memory (as the java.util.logging framework will try to keep all the information that needs to be written to disk in memory until it can be flushed).

 

If the java.util.logging framework cannot write the log file as fast as log entries are created, you will run into OutOfMemory errors. The best approach is to log only the necessary information, and, if needed, check to see Logger.isDebugEnabled() before writing out debugging log messages. The logging level can be changed from the logging configuration file.

 

Logging Exceptions

From the previous recipes you learned how to catch exceptions and how to log information. This recipe will put these two recipes together.

 

Problem You want to record exceptions in your log file.

Solution

Configure your application to use SLF4J. Utilize try/catch blocks to log exceptions within the error log. In the following example, an SLF4J Logger is used to log messages from within an exception handler.

static Logger rootLogger = LoggerFactory.getLogger(""); private void start() {
loadLoggingConfiguration();
Thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> {
rootLogger.error("Error in thread "+t+" caused by ",e);
});
int c = 20/0;
}
private void loadLoggingConfiguration() {
FileInputStream ins = null;
try {
ins = new FileInputStream(new File("logging.properties")); LogManager.getLogManager().readConfiguration(ins);
} catch (IOException e) {
e.printStackTrace();
}
}

 

How It Works

The example demonstrates how to use an UncaughtExceptionHandler in conjunction with SLF4J to log exceptions to a logging file. When logging an exception, it is good to include the stack trace showing where the exception was thrown. In the example, a thread contains an UncaughtExceptionHandler, which utilizes a lambda expression containing a logger. The logger is used to write any caught exceptions to a log file.

 

Note If an exception is thrown repeatedly, the JVM tends to stop populating the stack trace in the Exception object. This is done for performance reasons because retrieving the same stack trace becomes expensive. If this happens, you will see an exception with no stack trace being logged. When that happens, check the log’s previous entries and see whether the same exception was thrown. If the same exception has been thrown previously, the full stack trace will be present on the first logged instance of the exception.

 

Logging with the Unified JVM Logger

Problem You wish to perform logging of JVM processes and you want to have fine-grained control over the logging.

Solution

Utilize the unified JVM logger utility that was added as part of Java 9. In the following solution, the JVM logger utility is configured to perform logging and direct to a file on the operating system.

To initiate the logging, open the command prompt or terminal, and execute the following statement:

java -Xlog:all:file=test.txt:time,level

 

The statement will configure the JVM to log all tags to a file named test.txt. The decorations that will be logged are time and level. The next example demonstrates how to log tags using ‘gc’ using ‘trace’ level to stdout using the ‘uptime’ decoration.

java –Xlog:gc=trace:uptime

 

How It Works

Logging for the JVM has been enhanced with the release of Java 9 to allow a single unified system offering fine-grained control. In the past, logging a JVM system-level component could become a time consuming task since it was difficult to pinpoint the root causes of many issues.

The updated logging facility provides the following features:

  • Common command-line options for logging various JVM processes
  • Tag categorization
  • Differentiation between logging levels
  • Ability to log to a file
  • File rotation capability
  • Dynamic configuration

 

To configure the JVM logging, execute the java.exe with the –Xlog flag, appending options to the flag separated by a colon [:]. If you wish to perform logging for a single run of the JVM, include the –Xlog flag when invoking the Java application.

 

There are several options available for the –Xlog flag that indicate “what” to log, and “where” to log in the following format:

-Xlog[:option=<what:level>:<output>:<decorators>:<output-options>]

 

Note that in the format, you can specify –Xlog without any options to indicate that all tags should be logged, and all logging levels will go to stdout. In the solution, we saw that to configure logging of all tags, you may also specify the “all” option.

Omitting the <what> portion will default to tag-set “all” with a level of “info.” Ommitting the <level> will default to “info.” The available decorators are listed in Table, and omitting them altogether defaults to “uptime,” “level,” “tags.”

 

Table  Xlog Decorators

Time Current time and date (ISO-8601)
Uptime Amount of time surpassed since the start of the JVM (seconds and milliseconds)
Timemillis System.currentTimeMillis() output
Uptimemillis Milliseconds surpassed since the start of the JVM
Timenanos System.nanoTime() output
Uptimenanos Nanoseconds surpassed since the start of the JVM
Pid Process identifier
Tid Thread identifier
Level Associated log message level
Tags Associated log message tag

 

Three types of output are supported: stdout, stderr, and text file. Output can be configured to rotate files, limit file size, and so on by specifying output options. The possible output options include:

filecount=<file count>

filesize=<file size in kb>

parameter=value

The logging API can be controlled at runtime via the jcmd diagnostic commands utility. All of the options available at the command line are also available via the utility.

 

Note Help on the JVM logging utility is available using the –Xlog:help switch. This switch will print usage syntax and available tags, levels, decorators, and examples.

 

Media with JavaFX

• JavaFX provides a media-rich API capable of playing audio and video. The Media API allows developers to incorporate audio and video into their Rich Client Applications. One of the main benefits of the Media API is its cross-platform abilities when distributing media content via the web. With a range of devices (tablets, music players, TVs, and so on) that need to play multimedia content, the need for a cross-platform API is essential.

 

Imagine a not-so-distant future where your TV or wall is capable of interacting with you in ways that you’ve never dreamed possible. For instance, while viewing a movie you could select items or clothing used in the movie to be immediately purchased, all from the comfort of your home. With this future in mind, developers seek to enhance the interactive qualities of their media-based applications.

In this blog you will learn how to play audio and video in an interactive way.

 

Playing Audio

Problem You want to code an application that will allow you to listen to music and become entertained with a graphical visualization.

Solution

Create an MP3 player by utilizing the following classes:

javafx.scene.media.Media
javafx.scene.media.MediaPlayer
javafx.scene.media.AudioSpectrumListener
The following source code is an implementation of a simple MP3 player:
package org.java9recipes.blog16.recipe16_01;
import java.io.File;
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.media.AudioSpectrumListener; import javafx.scene.media.Media; import javafx.scene.media.MediaPlayer;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class PlayingAudio extends Application {
private MediaPlayer mediaPlayer;
private Point2D anchorPt;
private Point2D previousLocation;
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(final Stage primaryStage) { primaryStage.setTitle("blog 16-1 Playing Audio"); primaryStage.centerOnScreen(); primaryStage.initStyle(StageStyle.TRANSPARENT);
Group root = new Group();
Scene scene = new Scene(root, 551, 270, Color.rgb(0, 0, 0, 0));
// application area
Rectangle applicationArea = new Rectangle();
applicationArea.setArcWidth(20);
applicationArea.setArcHeight(20);
applicationArea.setFill(Color.rgb(0, 0, 0, .80));
applicationArea.setX(0);
applicationArea.setY(0);
applicationArea.setStrokeWidth(2);
applicationArea.setStroke(Color.rgb(255, 255, 255, .70));
root.getChildren().add(applicationArea);
applicationArea.widthProperty().bind(scene.widthProperty()); applicationArea.heightProperty().bind(scene.heightProperty());
final Group phaseNodes = new Group();
root.getChildren().add(phaseNodes);
starting initial anchor point scene.setOnMousePressed((MouseEvent event) -> {
anchorPt = new Point2D(event.getScreenX(), event.getScreenY());
});
dragging the entire stage
scene.setOnMouseDragged((MouseEvent event) -> {
if (anchorPt != null && previousLocation != null) { primaryStage.setX(previousLocation.getX() + event.getScreenX() - anchorPt. getX());
primaryStage.setY(previousLocation.getY() + event.getScreenY() - anchorPt. getY());
}
});
set the current location scene.setOnMouseReleased((MouseEvent event) -> {
previousLocation = new Point2D(primaryStage.getX(), primaryStage.getY());
});
Dragging over surface
scene.setOnDragOver((DragEvent event) -> {
Dragboard db = event.getDragboard();
if (db.hasFiles()) {
event.acceptTransferModes(TransferMode.COPY); } else {
event.consume();
}
});
Dropping over surface scene.setOnDragDropped((DragEvent event) -> {
Dragboard db = event.getDragboard(); boolean success = false;
if (db.hasFiles()) { success = true; String filePath = null;
for (File file : db.getFiles()) { filePath = file.getAbsolutePath(); System.out.println(filePath);
}
// play file
Media media = new Media(new File(filePath).toURI().toString());
if (mediaPlayer != null) {
mediaPlayer.stop();
}
mediaPlayer = new MediaPlayer(media);
Maintained Inner Class for Tutorial, could be changed to lambda mediaPlayer.setAudioSpectrumListener(new AudioSpectrumListener() {
@Override
public void spectrumDataUpdate(double timestamp, double duration, float[] magnitudes, float[] phases) {
phaseNodes.getChildren().clear(); int i = 0;
int x = 10; int y = 150;
final Random rand = new Random(System.currentTimeMillis()); for (float phase : phases) {
int red = rand.nextInt(255); int green = rand.nextInt(255); int blue = rand.nextInt(255);
Circle circle = new Circle(10); circle.setCenterX(x + i); circle.setCenterY(y + (phase * 100)); circle.setFill(Color.rgb(red, green, blue, .70)); phaseNodes.getChildren().add(circle); i += 5;
}
}
});
mediaPlayer.setOnReady(mediaPlayer::play);
}
event.setDropCompleted(success);
event.consume();
});
// create slide controls
final Group buttonGroup = new Group();
// rounded rect
Rectangle buttonArea = new Rectangle();
buttonArea.setArcWidth(15);
buttonArea.setArcHeight(20);
buttonArea.setFill(new Color(0, 0, 0, .55));
buttonArea.setX(0);
buttonArea.setY(0);
buttonArea.setWidth(60);
buttonArea.setHeight(30);
buttonArea.setStroke(Color.rgb(255, 255, 255, .70));
buttonGroup.getChildren().add(buttonArea);
// stop audio control
Rectangle stopButton = new Rectangle();
stopButton.setArcWidth(5);
stopButton.setArcHeight(5);
stopButton.setFill(Color.rgb(255, 255, 255, .80));
stopButton.setX(0);
stopButton.setY(0);
stopButton.setWidth(10);
stopButton.setHeight(10);
stopButton.setTranslateX(15);
stopButton.setTranslateY(10); stopButton.setStroke(Color.rgb(255, 255, 255, .70));
stopButton.setOnMousePressed((MouseEvent me) -> { if (mediaPlayer != null) {
mediaPlayer.stop();
}
});
buttonGroup.getChildren().add(stopButton);
// play control
final Arc playButton = new Arc(); playButton.setType(ArcType.ROUND); playButton.setCenterX(12); playButton.setCenterY(16); playButton.setRadiusX(15); playButton.setRadiusY(15); playButton.setStartAngle(180 - 30); playButton.setLength(60); playButton.setFill(new Color(1, 1, 1, .90)); playButton.setTranslateX(40);
playButton.setOnMousePressed((MouseEvent me) -> { mediaPlayer.play();
});
// pause control
final Group pause = new Group();
final Circle pauseButton = new Circle(); pauseButton.setCenterX(12); pauseButton.setCenterY(16); pauseButton.setRadius(10); pauseButton.setStroke(new Color(1, 1, 1, .90)); pauseButton.setTranslateX(30);
final Line firstLine = new Line();
firstLine.setStartX(6);
firstLine.setStartY(16 - 10);
firstLine.setEndX(6);
firstLine.setEndY(16 - 2);
firstLine.setStrokeWidth(3);
firstLine.setTranslateX(34);
firstLine.setTranslateY(6);
firstLine.setStroke(new Color(1, 1, 1, .90));
final Line secondLine = new Line();
secondLine.setStartX(6);
secondLine.setStartY(16 - 10);
secondLine.setEndX(6);
secondLine.setEndY(16 - 2);
secondLine.setStrokeWidth(3);
secondLine.setTranslateX(38);
secondLine.setTranslateY(6);
secondLine.setStroke(new Color(1, 1, 1, .90));
pause.getChildren().addAll(pauseButton, firstLine, secondLine);
pause.setOnMousePressed((MouseEvent me) -> { if (mediaPlayer != null) {
buttonGroup.getChildren().remove(pause);
buttonGroup.getChildren().add(playButton);
mediaPlayer.pause();
}
});
playButton.setOnMousePressed((MouseEvent me) -> { if (mediaPlayer != null) {
buttonGroup.getChildren().remove(playButton); buttonGroup.getChildren().add(pause); mediaPlayer.play();
}
});
buttonGroup.getChildren().add(pause);
// move button group when scene is resized
buttonGroup.translateXProperty().bind(scene.widthProperty().subtract(buttonArea. getWidth() + 6));
buttonGroup.translateYProperty().bind(scene.heightProperty().subtract(buttonArea.
getHeight() + 6));
root.getChildren().add(buttonGroup);
// close button
final Group closeApp = new Group();
Circle closeButton = new Circle();
closeButton.setCenterX(5);
closeButton.setCenterY(0);
closeButton.setRadius(7);
closeButton.setFill(Color.rgb(255, 255, 255, .80));
Node closeXmark = new Text(2, 4, "X");
closeApp.translateXProperty().bind(scene.widthProperty().subtract(15));
closeApp.setTranslateY(10);
closeApp.getChildren().addAll(closeButton, closeXmark);
closeApp.setOnMouseClicked((MouseEvent event) -> {
Platform.exit();
});
root.getChildren().add(closeApp);
primaryStage.setScene(scene);
primaryStage.show();
previousLocation = new Point2D(primaryStage.getX(), primaryStage.getY());
}
}

 

How It Works

Before you get started, I’ll discuss the instructions on how to operate the MP3 player application that is created. The users will be able to drag and drop an audio file into the application area to be played. Located on the lower right of the application are buttons to stop, pause, and resume play of audio media.

 

As the music is playing, the user will also notice randomly colored balls bouncing around to the music. Once the users are done listening to the music, they can quit the application by clicking the white rounded close button located in the upper right corner. Instead of image files, however, the user is accessing audio files. JavaFX currently supports the following audio file formats: .mp3, .wav, and .aiff.

 

Following the same look and feel, you will use the same style. In this recipe, you modify the button controls to resemble buttons, similar to many media player applications. When the pause button is pressed, it will pause the audio media from playing and toggle to the play button control, thus allowing the users to resume.

 

As an added bonus, the MP3 player will appear as an irregular shaped, semitransparent window without borders that can also be dragged around the desktop using the mouse. Now that you know how the music player will operate, let’s walk through the code.

 

First, you need to create instance variables that will maintain state information for the lifetime of the application. Table 16-1 describes all the instance variables used in this music player application. The first variable is a reference to a media player (MediaPlayer) object that will be created in conjunction with a Media object containing an audio file.

 

Next, you create an anchorPt variable used to save the starting coordinate of a mouse press when the users begin to drag the window across the screen. When calculating the upper left bounds of the application window during a mouse-dragged operation, the previousLocation variable will contain the previous window’s screen X and Y coordinates.

 

Here, I wanted to raise the bar a little by showing you how to create irregularly shaped semitransparent windows, thus making things look more hip or modern. As you begin to create the media player, you’ll notice in the start() method that you prepare the Stage object by initializing the style using StageStyle.TRANSPARENT.

 

After you initialize the style to StageStyle.TRANSPARENT, the window will be undecorated, with the entire window area’s opaque value set to zero (invisible). The following code shows you how to create a transparent window without a title bar or windowed borders:

primaryStage.initStyle(StageStyle.TRANSPARENT);

 

With the invisible stage, you create a rounded rectangular region that will be the application’s surface or main content area. Next, notice the width and height of the rectangle bound to the scene object in case the window is resized. Because the window isn’t going to be resized, the bind isn’t necessary (it will be needed, however, when you provide the ability to enlarge a video screen to take on a full-screen mode).

 

After creating a black, semitransparent, rounded rectangular area (applicationArea), you’ll be creating a simple Group object to hold all the randomly colored Circle nodes that will show off graphical visualizations while the audio is being played. Later, you will see how the phaseNodes (Group) variable is updated based on sound information using an AudioSpectrumListener.

 

Next, you add EventHandler<MouseEvent> instances to the Scene object (the example uses lambda expressions) to monitor mouse events as the user drags the window around the screen. The first event in this scenario is a mouse press, which will save the cursor’s current (X, Y) coordinates to the variable anchorPt. The following code is adding an EventHandler to the mouse-press property of the Scene:

starting initial anchor point scene.setOnMousePressed((MouseEvent event) -> {

anchorPt = new Point2D(event.getScreenX(), event.getScreenY());

});

 

After implementing the mouse-press event handler, you can create an EventHandler to the Scene’s mouse-drag property. The mouse–drag event handler will update and position the application window (Stage) dynamically, based on the previous window’s location (upper left corner) along with the anchorPt variable. Shown here is an event handler responsible for the mouse-drag event on the Scene object:

 

dragging the entire stage scene.setOnMouseDragged((MouseEvent event) -> {

if (anchorPt != null && previousLocation != null) { primaryStage.setX(previousLocation.getX() + event.getScreenX() - anchorPt.getX()); primaryStage.setY(previousLocation.getY() + event.getScreenY() - anchorPt.getY());

}

});

You will want to handle the mouse-release event to perform actions. Once the mouse is released, the event handler will update the previousLocation variable for subsequent mouse-drag events to move the application window about the screen. The following code snippet updates the previousLocation variable:

set the current location scene.setOnMouseReleased((MouseEvent event) -> {

previousLocation = new Point2D(primaryStage.getX(), primaryStage.getY());

});

 

Next, you will be implementing the drag-and-drop scenario to load the audio file from the file system (using the File Manager). When handling a drag-and-drop scenario, in which you created an EventHandler to handle DragEvents. Instead of loading image files, you’ll be loading audio files from the host file system.

 

For brevity, I simply mention the code lines of the drag-and-dropped event handler. Once the audio file is available, you will create a Media object by passing in the file as a URI. The following code snippet is how to create a Media object:

Media media = new Media(new File(filePath).toURI().toString());

 

Once you have created a Media object you will have to create an instance of a MediaPlayer in order to play the sound file. Both the Media and MediaPlayer objects are immutable, which is why new instances of each will be created every time the user drags a file into the application. Next, you will check the instance variable mediaPlayer for a previous instance to make sure it is stopped before creating a new MediaPlayer instance. The following code checks for a prior media player to be stopped:

if (mediaPlayer != null) {

mediaPlayer.stop();

}

So, here is where you create a MediaPlayer instance. A MediaPlayer object is responsible for controlling the playing of media objects. Notice that a MediaPlayer will treat sound or video media the same in terms of playing, pausing, and stopping media. When creating a media player, you specify the media and audioSpectrumListener attribute methods. Setting the autoPlay attribute to true will play the audio media immediately after it has been loaded.

 

The last thing to specify on the MediaPlayer instance is an AudioSpectrumListener. So, what exactly is this type of listener, you say? Well, according to the Javadoc, it is an observer receiving periodic updates of the audio spectrum. In layman’s terms, it is the audio media’s sound data such as volume, tempo, and so on.

 

To create an instance of an AudioSpectrumListener, you create an inner class that overrides the method spectrumDataUpdate(). You could have also used a lambda expression here; the example uses the inner class to provide better insight into the functionality. Table 16-2 lists all the inbound parameters for the audio spectrum listener’s method. For more details, refer to the Javadoc at http://docs.oracle.com/javase/8/javafx/api/javafx/scene/media/

AudioSpectrumListener.html.

 

In the example, randomly colored circle nodes are created, positioned, and placed on the scene based on the variable phases (array of floats). To draw each colored circle, the circle’s center X is incremented by five pixels and the circle’s center Y is added with each phase value multiplied by 100. Shown here is the code snippet that plots each randomly colored circle:

circle.setCenterX(x + i);
circle.setCenterY(y + (phase * 100));
... // setting the circle
i+=5;
Here is an inner class implementation of an AudioSpectrumListener:
new AudioSpectrumListener() {
@Override
public void spectrumDataUpdate(double timestamp, double duration, float[] magnitudes, float[] phases) {
phaseNodes.getChildren().clear();
int i = 0;
int x = 10;
int y = 150;
final Random rand = new Random(System.currentTimeMillis());
for(float phase:phases) {
int red = rand.nextInt(255);
int green = rand.nextInt(255);
int blue = rand.nextInt(255);
Circle circle = new Circle(10); circle.setCenterX(x + i); circle.setCenterY(y + (phase * 100)); circle.setFill(Color.rgb(red, green, blue, .70)); phaseNodes.getChildren().add(circle); i+=5;
}
}
};

 

Once the media player is created, you create a java.lang.Runnable to be set to the onReady attribute to be invoked when the media is in a ready state. Once the ready event is realized, the run() method will call the media player object’s play() method to begin the audio.

 

With the dragged-drop sequence completed, you notify the drag-and-drop system by invoking the event’s setDropCompleted() method with a value of true. The following code snippet demonstrates how to implement a Runnable to begin the media player as soon as the media player is in a ready state using a method reference:

mediaPlayer.setOnReady(mediaPlayer::play);

 

Finally, create buttons with JavaFX shapes to represent the stop, play, pause, and close buttons. When creating shapes or custom nodes, you can add event handlers to nodes in order to respond to mouse clicks. Although there are advanced ways to build custom controls in JavaFX, this example uses custom-built button icons from simple rectangles, arcs, circles, and lines.

 

To see more advanced ways to create custom controls, refer to the Javadoc on the Skinnable API. To attach event handlers for a mouse press, simply call the setOnMousePress() method by passing in an EventHandler<MouseEvent> instance. The following code demonstrates adding an EventHandler to respond to mouse press on the stopButton node:

stopButton.setOnMousePressed((MouseEvent me) -> { if (mediaPlayer != null) {

mediaPlayer.stop();

}

});

 

Because all the buttons use the same code snippet, only the method calls that each button will perform on the media player are listed. The last button, Close, isn’t related to the media player, but it provides a way to exit the MP3 player application. The following actions are responsible for stopping, pausing, playing, and exiting the MP3 player application:

Stop - mediaPlayer.stop();

Pause - mediaPlayer.pause();

Play - mediaPlayer.play();

Close - Platform.exit();

 

Playing Video

Problem You want to create an application to view a video file complete with controls to play, pause, stop, and seek.

Solution

Create a video media player application by utilizing the following classes:
javafx.scene.media.Media
javafx.scene.media.MediaPlayer
javafx.scene.media.MediaView
The following code is an implementation of a JavaFX basic video player:
public void start(final Stage primaryStage) { primaryStage.setTitle("blog 16-2 Playing Video"); primaryStage.centerOnScreen(); primaryStage.initStyle(StageStyle.TRANSPARENT);
final Group root = new Group();
final Scene scene = new Scene(root, 540, 300, Color.rgb(0, 0, 0, 0));
rounded rectangle with slightly transparent Node applicationArea = createBackground(scene); root.getChildren().add(applicationArea);
allow the user to drag window on the desktop attachMouseEvents(scene, primaryStage);
allow the user to see the progress of the video playing progressSlider = createSlider(scene); root.getChildren().add(progressSlider);

 

Dragging over surface

scene.setOnDragOver((DragEvent event) -> {
Dragboard db = event.getDragboard();
if (db.hasFiles() || db.hasUrl() || db.hasString()) {
event.acceptTransferModes(TransferMode.COPY); if (mediaPlayer != null) {
mediaPlayer.stop();
}
} else { event.consume();
}
});
// update slider as video is progressing (later removal)
progressListener = (ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) -> {
progressSlider.setValue(newValue.toSeconds());
};
Dropping over surface scene.setOnDragDropped((DragEvent event) -> {
Dragboard db = event.getDragboard();
boolean success = false;
URI resourceUrlOrFile = null;
dragged from web browser address line? if (db.hasContent(DataFormat.URL)) {
try {
resourceUrlOrFile = new URI(db.getUrl());
} catch (URISyntaxException ex) { ex.printStackTrace();
}
} else if (db.hasFiles()) {
dragged from the file system String filePath = null;
for (File file:db.getFiles()) { filePath = file.getAbsolutePath();
}
resourceUrlOrFile = new File(filePath).toURI();
success = true;
}
// load media
Media media = new Media(resourceUrlOrFile.toString());
stop previous media player and clean up if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.currentTimeProperty().removeListener(progressListener); mediaPlayer.setOnPaused(null); mediaPlayer.setOnPlaying(null); mediaPlayer.setOnReady(null);
}
create a new media player
mediaPlayer = new MediaPlayer(media);
as the media is playing move the slider for progress mediaPlayer.currentTimeProperty().addListener(progressListener);
play video when ready status
mediaPlayer.setOnReady(() -> {
progressSlider.setValue(1);
progressSlider.setMax(mediaPlayer.getMedia().getDuration().toMillis()/1000);
mediaPlayer.play();
});
Lazy init media viewer if (mediaView == null) {
mediaView = new MediaView(); mediaView.setMediaPlayer(mediaPlayer); mediaView.setX(4); mediaView.setY(4); mediaView.setPreserveRatio(true);
mediaView.setOpacity(.85);
mediaView.setSmooth(true);
mediaView.fitWidthProperty().bind(scene.widthProperty().subtract(220)); mediaView.fitHeightProperty().bind(scene.heightProperty().subtract(30));
make media view as the second node on the scene. root.getChildren().add(1, mediaView);
}
sometimes loading errors occur, print error when this happens mediaView.setOnError((MediaErrorEvent event1) -> {
event1.getMediaError().printStackTrace();
});
mediaView.setMediaPlayer(mediaPlayer);
event.setDropCompleted(success);
event.consume();
});
// rectangular area holding buttons
final Group buttonArea = createButtonArea(scene);
stop button will stop and rewind the media Node stopButton = createStopControl();
play button can resume or start a media final Node playButton = createPlayControl();
pause media play
final Node pauseButton = createPauseControl();
stopButton.setOnMousePressed((MouseEvent me) -> { if (mediaPlayer!= null) {
buttonArea.getChildren().removeAll(pauseButton, playButton); buttonArea.getChildren().add(playButton); mediaPlayer.stop();
}
});
pause media and swap button with play button
pauseButton.setOnMousePressed((MouseEvent me) -> { if (mediaPlayer!=null) {
buttonArea.getChildren().removeAll(pauseButton, playButton); buttonArea.getChildren().add(playButton); mediaPlayer.pause();
paused = true;
}
});
play media and swap button with pause button playButton.setOnMousePressed((MouseEvent me) -> {
if (mediaPlayer != null) { buttonArea.getChildren().removeAll(pauseButton, playButton); buttonArea.getChildren().add(pauseButton);
paused = false; mediaPlayer.play();
}
});
add stop button to button area
buttonArea.getChildren().add(stopButton);
set pause button as default buttonArea.getChildren().add(pauseButton);
add buttons
root.getChildren().add(buttonArea);
// create a close button
Node closeButton= createCloseButton(scene);
root.getChildren().add(closeButton);
primaryStage.setOnShown((WindowEvent we) -> {
previousLocation = new Point2D(primaryStage.getX(), primaryStage.getY());
});
primaryStage.setScene(scene);
primaryStage.show();
}
Following is the attachMouseEvents() method, which adds an EventHandler to the scene so the video player can enter full-screen mode.
private void attachMouseEvents(Scene scene, final Stage primaryStage) {
Full screen toggle scene.setOnMouseClicked((MouseEvent event) -> {
if (event.getClickCount() == 2) { primaryStage.setFullScreen(!primaryStage.isFullScreen());
}
});
... // the rest of the EventHandlers
}
The following method creates a slider control with a ChangeListener to enable the users to search backward and forward through the video:
private Slider createSlider(Scene scene) {
Slider slider = new Slider();
slider.setMin(0);
slider.setMax(100);
slider.setValue(1);
slider.setShowTickLabels(true);
slider.setShowTickMarks(true);
slider.valueProperty().addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
if (paused) {
long dur = newValue.intValue() * 1000;
mediaPlayer.seek(new Duration(dur));
}
});
slider.translateYProperty().bind(scene.heightProperty().subtract(30)); return slider;
}

 

How It Works

To create a video player, you will model the application similar to the example in Recipe 16-1 by reusing the same application features such as drag-and-drop files, media button controls, and so on. For the sake of clarity, I took the previous recipe and moved much of the UI code into convenience functions so you will be able to focus on the Media APIs without getting lost in the UI code.

 

The rest of the recipes in this blog consist of adding simple features to the JavaFX basic media player created in this recipe. This being said, the code snippets in the following recipes will be brief, consisting only of the necessary code for each new desired feature.

It is important to note that the JavaFX media player supports various media formats. The supported formats are as follows:
AIFF
FXM, FLV
HLS (*)
MP3
MP4
WAV


For a complete summary of the supported media types, see the online documentation at http://docs.

http://oracle.com/javase/8/javafx/api/javafx/scene/media/package-summary.html.

 

Just like the audio player created in the last recipe, the JavaFX basic video player has the same basic media controls, including stop, pause, and play. In addition to these simple controls, you’ve added new capabilities such as seeking and full-screen mode.

 

When playing a video you’ll need a view area (javafx.scene.media.MediaView) to show it. You also create a slider control to monitor the progress of the video. The slider control allows the users to seek backward and forward through the video. One last bonus feature is enabling the video to become full screen by double-clicking the application window. To restore the window, users repeat the double-click or press Escape.

 

To quickly get started, let’s jump into the code. After setting the stage in the start() method, you create a black semitransparent background by calling the createBackground() method (applicationArea). Next, the attachMouseEvents() method is invoked to set up the EventHandlers so they can enable the users to drag the application window around the desktop. Another EventHandler to be attached to the scene will allow the users to switch to full-screen mode.

 

A conditional is used to check for a double-click in the application window in order to invoke full-screen mode. Once the double-click is performed, the Stage’s method setFullScreen() is invoked with a Boolean value opposite of the currently set value. Shown here is the code needed to make a window go to full-screen mode:

Full screen toggle scene.setOnMouseClicked((MouseEvent event) -> {

if (event.getClickCount() == 2) { primaryStage.setFullScreen(!primaryStage.isFullScreen());

}

});

 

As you continue the steps inside the start() method, a slider control is created by calling the convenience method createSlider(). The createSlider() method instantiates a Slider control and adds a ChangeListener to move the slider as the video is playing. The ChangeListener’s changed() method is invoked any time the slider’s value changes.

 

Once the changed() method is invoked you will have an opportunity to see the old and new values. The following code creates a ChangeListener to update the slider as the video is being played:

update slider as video is progressing (later removal) progressListener = (ObservableValue<? extends Duration> observable,

Duration oldValue, Duration newValue) -> { progressSlider.setValue(newValue.toSeconds());

};

After creating the progress listener (progressListener), the drag-dropped EventHandler for the scene needs to be created. The goal is to determine whether the pause button was pressed before the user can move the slider. Once a slider.isPressed() flag is determined, you will obtain the new value to be converted to milliseconds.

 

The dur variable is used to move the mediaPlayer to seek the position into the video as the user slides the control left or right. The ChangeListener’s changed() method is invoked any time the slider’s value changes. The following code is responsible for moving the seek position into the video based on the user moving the slider.

slider.valueProperty().addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
if (slider.isPressed()) {
long dur = newValue.intValue() * 1000;
mediaPlayer.seek(new Duration(dur));
}
});

 

Moving right along, you next implement a drag-dropped EventHandler to handle the media file being dropped into the application window area. Here the example first checks to see whether there was a previous mediaPlayer. If there was, the previous mediaPlayer object is stopped and cleanup is performed:

stop previous media player and clean up if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.currentTimeProperty().removeListener(progressListener);
mediaPlayer.setOnPaused(null); mediaPlayer.setOnPlaying(null); mediaPlayer.setOnReady(null);
}
...
play video when ready status mediaPlayer.setOnReady(() -> { progressSlider.setValue(1);
progressSlider.setMax(mediaPlayer.getMedia().getDuration().toMillis() / 1000); mediaPlayer.play();
});// setOnReady()

 

As with the audio player, you create a Runnable instance to be run when the media player is in a ready state. You’ll notice also that the progressSlider control uses values in seconds.

 

Once the media player object is in a ready state, a MediaView instance is created to display the media. The following code creates a MediaView object to be placed into the scene graph to display video content:

Lazy init media viewer if (mediaView == null) {
mediaView = new MediaView(); mediaView.setMediaPlayer(mediaPlayer); mediaView.setX(4); mediaView.setY(4); mediaView.setPreserveRatio(true); mediaView.setOpacity(.85); mediaView.setSmooth(true);
mediaView.fitWidthProperty().bind(scene.widthProperty().subtract(220)); mediaView.fitHeightProperty().bind(scene.heightProperty().subtract(30));
make media view as the second node on the scene. root.getChildren().add(1, mediaView);
}
sometimes loading errors occur, print error when this happens mediaView.setOnError((MediaErrorEvent event1) -> {
event1.getMediaError().printStackTrace();
});
mediaView.setMediaPlayer(mediaPlayer);
event.setDropCompleted(success);
event.consume();
});

 

Whew! You are finally finished with the scene’s drag-dropped EventHandler. Up next is pretty much the rest of the media button controls. The only difference is a single instance variable named paused of type Boolean that denotes whether the video was paused. The following code shows the pauseButton and playButton controlling the mediaPlayer object and setting the paused flag accordingly:

pause media and swap button with play button
pauseButton.setOnMousePressed((MouseEvent me) -> { if (mediaPlayer != null) {
buttonArea.getChildren().removeAll(pauseButton, playButton); buttonArea.getChildren().add(playButton); mediaPlayer.pause();
paused = true;
}
});
play media and swap button with pause button playButton.setOnMousePressed((MouseEvent me) -> {
if (mediaPlayer != null) { buttonArea.getChildren().removeAll(pauseButton, playButton); buttonArea.getChildren().add(pauseButton);
paused = false; mediaPlayer.play();
}
});

That is how you create a video media player. In the next recipe, you learn how to listen to media events and invoke actions.