Internationalisation (I18N)
The Date and Time API
In Java, an Instant
represents a point on the time line. The origin (called epoch) is arbitrarily set at midnight
of the first of January 1970 Greenwich time (UTC). Instant.now()
returns the current point on the time line,
being the number of seconds since the epoch (stored as long
) and the number of nano seconds (stored as int
). Each
day has 24x60x60 = 86400 seconds. A Duration
is the amount of time between two instants. Both Instant
and Duration
are immutable.
An Instant
does not have a human readable format. The two kinds of human time in the java API are local date/time and
zoned time. Local date/time has a date and/or time of day, but no associated time zone. Examples:
Local Date example.
Local Time Example.
Do not use zoned time, unless really necessary. Today’s date can be retrieved using LocalDate.now()
, an arbitrary date
with LocalDate.of(<year>, <month>, <day>)
like LocalDate.of(2021, 5, 1)
being first of May 2021. The amount of time
between dates is called a Period
. Check the java API to read about convenient methods of LocalDate
and Period
. An
interesting one is the datesUntil(…)
method on LocalDate
, which returns a Stream<LocalDate>
.
LocalTime
objects can be created like LocalDates, e.g.: LocalTime endOfLecture = LocalTime.now().plusHours(1);
Zoned times make use of time zomes. Java uses the IANA database: https://www.iana.org/time-zones
To make a zoned time out of a LocalDateTime
you can use the ZonedDateTime.of()
method or do it like this:
ZonedDateTime current = LocalDateTime.now().atZone(ZoneId.of("Europe/Amsterdam"));
. Now you get a specific Instant
in time.
The other way around, you can can find the corresponding ZonedDateTime
in another time zone using instant.atZone(…)
.
To represent dates and times in readable and customizable formats, use the DateTimeFormatter
class. It can use predefined
standard formatters, Locale
specific formatters and formatters with custom patterns.
This paragraph is based on Core Java Volume 2, 11th edition, chapter 6 by Cay Horstmann.
Number Formats
Some of you might have noticed that one of the teacher tests in the FXTriangulate exercise fails on your machine.
This is because on the machine we developed, the locale is set to en_US.
This causes the numbers to be formatted in the way that Double.parseDouble(String input)
expects it.
If you run the same tests on a machine with say a German Locale, the test that reads the length back from the label,
use Double.parseDouble(), which is then surprised to find a comma instead of the decimal point, and fails with a format exception.
To solve that, modify the test in method tLength such that the linedouble l = Double.parseDouble( ltext.split( ":" )[ 1 ] );
readsdouble l = getDoubleConsideringLocale( ltext.split( ":" )[ 1 ] );
,
so that the whole test method reads
@ParameterizedTest
@CsvSource( {
"a,greenCircle,blueCircle,500.0,400.0,100.0,100.0,500.0",
"b,redCircle,blueCircle,400.0,100.0,100.0,100.0,500.0",
"c,greenCircle,redCircle,300.0,100.0,100.0,400.0,100.0", } )
public void tLength( String line, String p1, String p2, double expected,
double x1, double y1, double x2, double y2 ) throws ParseException {
double xOrg = stage.getX();
double yOrg = stage.getY();
FxRobot rob = new FxRobot();
rob.drag( '#' + p1 ).dropTo( xOrg + x1, yOrg + y1 );
rob.drag( '#' + p2 ).dropTo( xOrg + x2, yOrg + y2 );
String ltext = labelMap.get( line ).apply( triangulator ).getText();
double l = getDoubleConsideringLocale( ltext.split( ":" )[ 1 ] ); 1
assertThat( l ).isCloseTo( expected, within( 0.1 ) );
// fail( "method tLength reached end. You know what to do." );
}
- changed line.
/**
* Use the default locale to parse a double value from a string.
* @param input string
* @return the double
* @throws ParseException if the string does not parse to double.
*/
static double getDoubleConsideringLocale( String input )
throws ParseException {
return DecimalFormat.getNumberInstance().parse( input ).doubleValue(); 1
}
/**
* Use the given locale to parse a double value from a string.
* @param locale to use.
* @param input string.
* @return the double.
* @throws ParseException if the string does not parse to double.
*/
<<<<<<< Updated upstream
static double getDoubleConsideringLocale( Locale locale, String input )
throws ParseException {
=======
static double getDoubleConsideringLocale( Locale locale, String input )
throws ParseException {
>>>>>>> Stashed changes
return DecimalFormat.getNumberInstance(locale).parse( input ).doubleValue();
}
- DecimalFormat.getNumberInstance gets a format that understands the default locale.
Locale.setDefault( Locale.GERMANY ); 1
- Set the locale to GERMANY if it isn’t already. Similar for other languages.
Testing Localized Exceptions
The standard way of testing exceptions with assertj is explained in week01.
To get to the localized message, which contains the message as translated by the locale framework is a bit more involved.
Luckily, AssertJ allows you to extract information from a Throwable, by using an extractor function. Now the Lambda bells should ring.
String[] keys = keyWords.split( "\\|");
assertThatThrownBy( () -> {
MainSimulation.main( args );
} ).isExactlyInstanceOf( exceptionMap.get( expectionClassName ) )
.extracting( e -> e.getLocalizedMessage() ) 1
.asString() 2
.contains( keys ); 3
- extract using
Function<? super Throwable,T>
,e → getLocalizedMessage()
in this case. - Get the assertion for in String. Do not use
toString()
, because that produces a String, not an AbstractStringAssert. - And use the assert to check that the string contains the required key information.
If you turn on type hints in NetBeans-IDE (or in vscode/intelij) you can see what the type is on which you call contains(keys)
image::assertjtypehints.png
Additional Pointers
- If you haven’t read the Horstmann book but you need an introduction into Internationalization, read this tutorial from DZONE here. Make sure to read the bit about Resource Bundles, as you use them in the exercise for this week.
- Jakob Jenkov also has a tutorial on Java Internationalization
Exceptions Summary

Exception-Handling
- Throw statement (cfr.
throw new YourFavoriteException()
) try
block: the code that is executed in normal circumstances, but might throw an exception.catch
block: the code that is executed to handle an exception that was thrown in the try block. The so called catch–block-parameter specifies which exception type can be caught by thecatch
block.- Examples:
// Prompt the user to enter two integers
System.out.print( "Enter two integers: " );
int number1 = input.nextInt();
int number2 = input.nextInt();
try {
int result = quotient( number1, number2 );
System.out.println( number1 + " / " + number2 + " is "
+ result );
} catch ( ArithmeticException ex ) {
System.out.println( "Exception: an integer "
+ "cannot be divided by zero " );
}
System.out.println( "Execution continues ..." );
/**
* Set a new radius
*/
public void setRadius( double newRadius )
throws IllegalArgumentException {
if ( newRadius >= 0 ) {
radius = newRadius;
} else {
throw new IllegalArgumentException(
"Radius cannot be negative" );
}
}
Exception types
- System errors are thrown by JVM and represented in the
Error
class. TheError
class describes internal system errors. Such errors rarely occur. If one does, there is little you can do beyond notifying the user and trying to terminate the program gracefully. Errors are unchecked exceptions. However, often you are too late and the JVM will take over or might crash itself. Exception
describes errors caused by your program and external circumstances. These errors can be caught and handled by your program.RuntimeException
is caused by programming errors, such as bad casting, accessing an out-of-bounds array, and numeric errors. Typically the programmer (maybe you) is to blame.
Checked Exceptions vs. Unchecked Exceptions
RuntimeException
,Error
and their subclasses are known as unchecked exceptions. All other exceptions are known as checked exceptions, meaning that the compiler forces the programmer to check and deal with the exceptions.- In most cases, unchecked exceptions reflect programming logic errors that are not recoverable. For example:
- A
NullPointerException
is thrown if you access an object through a reference variable before an object is assigned to it; - an
IndexOutOfBoundsException
is thrown if you access an element in an array outside the bounds of the array.
- A
- Unchecked exceptions can occur anywhere in the program. To avoid cumbersome overuse of try-catch blocks, Java does not mandate you to write code to catch unchecked exceptions.
- Often runtime exceptions point to a logic fault in the program, such as forgetting a nullity check before dereferencing a reference result from a method, or violating a precondition.
- Null values often indicate that an expected value could not be produced, such as in a search. If you are designing an API with such search, better return an empty collection (List, Set, Map) instead. Empty collections are predefined and there for cheap to return. Also, since Java 8, the preferred return
type for some method that may of may not produce a result is
java.util.Optional<T>
. Usage explanation in Guide to Java 8 Optional on Baeldung.
- Null values often indicate that an expected value could not be produced, such as in a search. If you are designing an API with such search, better return an empty collection (List, Set, Map) instead. Empty collections are predefined and there for cheap to return. Also, since Java 8, the preferred return
type for some method that may of may not produce a result is
More on exception handling and finally
clause
- The code in the optional
finally
block is executed under all circumstances, regardless of whether an exception occurs in the try block or is caught. The code is called before the method is left.
Custom Exception
classes
You can roll your own exceptions, in case your "business" has requirements that demand this.
try {
setRadius(newRadius);
numberOfObjects++;
} catch (InvalidRadiusException ex) {
ex.printStackTrace();
}
public class InvalidRadiusException extends Exception {
private final double radius;
/**
* Construct an exception
*/
public InvalidRadiusException(double radius) {
super("Invalid radius " + radius);
this.radius = radius;
}
/**
* Return the radius
*/
public double getRadius() {
return radius;
}
}
try {
CircleWithRadiusException c1 = new CircleWithRadiusException(5);
c1.setRadius(-5);
CircleWithRadiusException c3 = new CircleWithRadiusException(0);
} catch (InvalidRadiusException ex) {
System.out.println(ex);
}
Try with resources.
File
is one of the classes that has operations (methods) that throw
exceptions. java.util.Scanner
implements AutoCloseable
and as such has a close()
method.
When constructed with file as argument, it will use said file as input and close the file, when the scanner is closed. It is good practice to close
a resource once it has been 'consumed', because this releases the resource. The purpose of such a close
operation is to signal that the resource should be brought to a state
that is safe and can be freed for other use.
Classes that have such properties are known by the generic name
resource, something that you need but is potentially scarce.
Since Java 7, we have a feature called try with resources . It adds a new construct to the try-catch block, namely
the resources that you are about to use. There can be more than one resource.
try (
File f = new File( "data.txt" );
Scanner scanner = new Scanner(f); 1
) {
String s1 = scanner.next();
} catch ( FileNotFoundException fnfe ) {
// deal with exceptions.
} 2
- Scanner is also autoclosable.
- No other catch needed, that is only required if you want to deal with the exceptions that could occur. No finally needed to close resources.
Once the try with resources is done with the resources, the resources
are automatically closed and freed. For this to work, the resource must implement the java.lang.Autoclosable
interface.
You also save yourselves the tedious work of closing when an exception
occurred. That is also taken care of. So when your Java level is
greater or equal to Java 7, use try-with-resources whenever you can, because it will save you the work
of closing the resources in the finally block.
You declare the resources in the try-with-resources in the natural order, the earlier resource being used by those further on. They will be closed in the reverse order of declaration. In the above case that would mean:
- Scanner scanner closed
- InputStream in closed
- File f closed
which is the natural order of freeing or closing the resources.
Choose: at least one of catch or finally block
Note that the compiler insists on having at least one of
- a catch block (multiple allowed) or
- a finally block (at most one)
So try{…}
is not allowed, but try(AutoCloseable resource){…}
is, because the compiler will construct an implicit finally block for you.
It is the same idea of having a default constructor, when you did not write any.
If you decide to not deal with any checked exception, your method can simply pass them on to the caller, and let the caller deal with the situation.
Chaining of exceptions
As can be seen from the class diagram in figure Exception hierarchy with Throwable at the top, all exceptions are descendants of java.lang.Throwable
.
Exceptions can be chained, that is, one exception can hold a reference to another exception that is the actual cause of the exception being thrown. (Since Java 1.4 for all Throwables).
If you look at the API doc of java.lang.Throwable
, you will see that it provides several constructors.
Three of them allow you to add as parameter of type Throwable, with the formal and aptly chosen name cause. The receiver of the Throwable (or should I say catcher) can inspect any descendant of Throwable about a deeper cause, using the method Throwable.getCause()
. This can be seen as chain of Throwables, also called nested exceptions.
Although you cannot simply iterate through these exceptions, getting all in a loop is simple:
Throwable t = ex.getCause();
while ( t != null ) {
// do something with t like print or getMessage;
t= t.getCause();
}
Logging
Logging can be a very powerful instrument to squeeze very much or very precise information from a running application. It can be configured without modifying the source code. Use logging instead of system.out.print
public static void main( String[] args ) {
Logger logger = Logger.getLogger( Main.class.getName() ); 1
logger.log( Level.FINE, 2
()-> String.format("Hello %1$s args length= %2$d ", 3
"world", args.length) 4
);
}
- Create a specific logger for this class.
- This log statement only does 'work' when the log level is set to FINE or higher (FINER, FINEST)
- Note the use of positional parameters in the format string. It can be quite useful. See it as a free and random tip.
- This lambda has the shape of a supplier and is only evaluated or computed when the logging level is active.
Replace soutv
by using a log macro
When you use a supplier to supply the message that is logged, when logging is active, logging can become cheap. To make this work, you might want to teach your IDE to help a bit here, by adding a keyboard macro that adds logging instead of simple System.out.println(…).
logv
definition for NetBeans IDElogger.fine( ()->"${EXP instanceof="<any>" default="exp"} = " + ${EXP});
Now logging becomes simpler than ever.
- First create a logger with the already define macro
logr
[tab] and then - inside the methods use
logv
[tab] instead of [black]soutv
wherever you would like to output stuff for debugging.
In your logging.properties file add the proper level for your class or as global and you can turn the logging on or off whenever you need to.
Configure logging
When you copy the file logging.properties from its default location $JAVA_HOME/conf/logging.properties
to your projects
directory, preferably under a conf
directory, you can modify and adapt that file to make logging effective in a very precise way,
by specifying what level you want for what class.
#.properties
handlers= java.util.logging.ConsoleHandler
.level=FINE 1
java.util.logging.ConsoleHandler.level=FINE
logdem.LogDem.level=FINE 2
- specify global level
- specify for one class Then in the java startup (for instance in a run script or in one of the IDE settings), make sure this java property specifies the file.
In your IDE add -Djava.util.logging.config.file=conf/logging.properties
to the java run actions.
Say the class that needs debugging is called logging.Main
. Then in the logging properties file (see above) add
logging.Main.level = FINE
, and make sure that you start the JVM with the proper login properties file.
Read a bit more about logging in the book and get some additional information using https://sematext.com/blog/java-logging/.