Thursday, June 25, 2026

Java: Format Date with java time

 This article was originally published on JRoller on March 11, 2017

An advantage of the DateTimeFormatter class from the java time package over the old DateFormat is that it is thread-safe. But if the only thing I have is a Date, how can I proceed?

The DateTimeFormatter's format method takes a TemporalAccessor as a parameter. Date has a toInstant() method that converts the Date to an Instant object that implements the TemporalAccessor interface. All that sounds pretty easy:

  Date date = new Date();
  DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME;
  System.out.println(df.format(date.toInstant()));

Unfortunately, that does not work. You en up with the following error:

Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: Year
	at java.time.Instant.getLong(Unknown Source)
	at java.time.format.DateTimePrintContext$1.getLong(Unknown Source)
	at java.time.format.DateTimePrintContext.getValue(Unknown Source)
	at java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.format(Unknown Source)

As it turns out, the only formatter that can be used with an Instant is DateTimeFormatter.ISO_INSTANT. And the output would be in UTC time zone and look like this: '2011-12-03T10:15:30Z'.

In fact, this is all very logical. The only information missing from a Date to know the correct time and date to print is the time zone. The old DateFormat class assumed you would want to use the current time zone. For java time, you have to explicitly sate it:

  Date date = new Date();
  DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME;
  System.out.println(df.format(date.toInstant().atZone(ZoneId.systemDefault())));

Or you can also provide the time zone to the formatter:

  Date date = new Date();
  DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME
    .withZone(ZoneId.systemDefault());
  System.out.println(df.format(date.toInstant()));

The toZone() method of Instant creates a ZonedDateTime object, from which you can easily convert to LocalDate, LocalTime or LocalDateTime. To convert back to a Date is always a matter of providing the missing information.

For instance, from a Localtime, you provide a date and a time zone:

  LocalTime now = LocalTime.now();
  Date date = Date.from(now.atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant());

From a LocalDate, you need a time and a time zone:

  LocalDate now = LocalDate.now();
  Date date = Date.from(now.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());

Java: Create Temporary Files with NIO 2 on Linux

This article was originally published on JRoller on October 18, 2006 

We have an application that had problems with memory consumption. One type of object that we were keeping in memory was kept there only in case a user would start an administration client and connect it to our app. So instead of having it constantly in memory, we decided to serialize it into a temporary file that would be destroyed when the application would stop. When an admin would connect, we would read back the data and send it before removing it from memory again.

The code that created the temporary file looked more or less like that:

  Path dir = Paths.get("myDir");
  Path tempFile = Files.createTempFile(dir, "MyApp", null);

  OutputStream outputStream = Files.newOutputStream(tempFile, StandardOpenOption.DELETE_ON_CLOSE);

As you see, the creation of a temporary file goes in two steps. First, the createTempFile() method would create a unique name with the given prefix. Then, the DELETE_ON_CLOSE option would delete the file when it was closed, that is in our case when the application would exit.

All our tests conducted in development on Windows platform would work fine. However, our test team ran all their tests on a Linux platform, which was actually the target platform for production. And guess what? It failed. The file would be created fine, and data was written without any problem. But when an admin would connect, the application would return a FileNotFoundException. When we checked, indeed, the files were not there.

After some investigation, it turned out that the DELETE_ON_CLOSE option on Linux platform would delete the file immediately from the file system, while keeping a reference to it in the Output Stream so that we could still write to it. How did we fix it? By turning to a good old non-NIO method:

  Path dir = Paths.get("myDir");
  Path tempFile = Files.createTempFile(dir, "MyApp", null);
  tempFile.toFile().deleteOnExit();
  OutputStream outputStream = Files.newOutputStream(tempFile);
I have the feeling that 10 years later, delete on exit is still not included in NIO.

Java: Negating a Predicate

 This article was originally publixhed on JRoller on September 12, 2016

Often, when using Java streams, I try to replace my lambdas with Method References. For instance, when I have this code:

    mystream.filter(mystring -> mystring.isEmpty()) ...

I tend to replace it with that one:

    mystream.filter(String::isEmpty) ...

Except that this code is not really useful. I rarely filter my stream with empty Strings. Usually, I do quite the opposite:

    mystream.filter(mystring -> !mystring.isEmpty()) ...

Now with that one small symbol, I managed to make my day more difficult. What to do if I really want to use Method References? I can create a isNotEmpty() method, but I don't want to do that for all my methods that return a boolean. If I really insist, I can end up with this ugly code:

    mystream.filter(((Predicate<String>)String::isEmpty).negate()) ...

One way to make things nice again, is to create a helper method, like this one:

    public static <T> Predicate<T> not(Predicate<T> p) {
        return p.negate();
    }

Now, I can simply use it like this:

    mystream.filter(not(String::isEmpty)) ...

I heard that such a method might find its way into JDK 9. Someone heard anything more concrete?

It turned out that Predicate.not() was introduced in Java 11.

Monday, June 1, 2026

AWS: Cloudshell Python version mismatch

 Lately, I tried to install a Python library onto Cloudshell. I first checked the Python version:

$ python --version

Python 3.13.13

So I ran pip install, which deployed a version of my lib compatible with Python 3.13. The lib declares some entry points for setuptools, so that I can run some commands from the CLI. So I run those commands from the shell, but they fail with some errors about missing import.

After investigation, I found out that the command scripts started with a shebang like this one:

#!/usr/bin/python3

This is all normal, except that if I check the link, this is what I find:

$ ls -l /usr/bin/python3

lrwxrwxrwx. 1 root root 9 Apr 20 22:07 /usr/bin/python3-> python3.9

Someone forgot something? Or am I looking at it wrong?

Wednesday, May 13, 2026

AWS: The State of Account State

 In September 2025, AWS announced that the Account information in the Organizations Service will have a new State field to replace the Status field. Since that date, both fields are available for all Organizations operations, but the Status field is vowed to be removed on September 2026.

When you read such an announcement and you know your code is using the Status field, you project to review your code and update it. So we did quite immediately, but we could not see the new State field when executing our lambdas. So we postponed the update for later.

Recently, I had another look at the problem, and still could not see any State field appearing in lambdas. I tested some call to DescribeAccount within CloudShell, but the field was really there. So I decided to run the following lambda:

import boto3
import botocore

def lambda_handler(event, context):
    print("boto3:", boto3.__version__)
    print("botocore:", botocore.__version__)

    org_client = boto3.client("organizations")
    response = org_client.describe_account(AccountId="123456789012")
    print(response)

I was surprised by the result.

boto3: 1.40.4
botocore: 1.40.4

Those versions were released in August 2025, before the update. CloudShell in my test uses botocore 1.42.72, which is from March this year. When I notified AWS Support about it, they just told me to use a Layer with a more recent botocore included. How long should I keep this temporary workaround?

Tuesday, May 12, 2026

AWS: Duplicates in Search Provisioned Products

 Using our beloved boto3 library, we are looking for the list of all our Provisioned Products in Service Catalog.

sc_client = boto3.client('servicecatalog')
result = sc_client.search_provisioned_products(
    PageSize=20
)

I won't bore you with th code that loops over the result and perform the operation again if we have more than 20 products. But the strange thing, is that wherever we had more than the page size, some products were repeated in the other pages. Thinking of a bug in AWS Service Catalog, we reached out to the Support Team. This is their answer:

This is a known behavior with the SearchProvisionedProducts API when using the default relevance-based sorting. Because results are sorted by relevance, the ordering can shift slightly between paginated requests, which causes duplicates (or occasionally missed items) across pages.

Never heard of relevance-based sorting. Looking at the documentation, there is no mention of it:

SortBy

The sort field. If no value is specified, the results are not sorted. The valid values are arnidname, and lastRecordId.

 Then, Support Team is proposing a solution:

Adding SortBy='createdTime' gives the pagination a stable ordering, so the page token points to a consistent boundary between pages. No more duplicates should appear regardless of how many provisioned products you have.

It is interesting to note that 'createdTime' is not listed in the documentation either. We tried it and it works. So a hidden feature solves a known bug.

Sunday, February 15, 2026

Collectors.toMap does not like null

 This article was originally published on JRoller on November 26, 2015

Some map accept null values, some don't. How do you know? You usually take a look in the javadoc. But what about maps created by streams through the Collectors.toMap? The javadoc does not say. So I tried out. I picked the following code:

 	List<String> player = Arrays.asList("Lebron", "Kobe", "Shaquille");
	List<String> team = Arrays.asList("Cleveland", "Los Angeles", null);
	
	Map<String, String> currentTeam = new HashMap<>();
	for (int i = 0; i < player.size(); i++) {
		currentTeam.put(player.get(i), team.get(i));
	}

Everything works as expected, it inserts the null value into my map. So I tried to convert it to streams (maybe not in the best way):

	Map<String, String> currentTeam = IntStream.range(0, player.size())
		.mapToObj(i -> i)
		.collect(Collectors.toMap(i -> player.get(i), i -> team.get(i)));

Here is what I get:

Exception in thread "main" java.lang.NullPointerException
	at java.util.HashMap.merge(HashMap.java:1216)
	at java.util.stream.Collectors.lambda$toMap$148(Collectors.java:1320)
	at java.util.stream.Collectors$$Lambda$6/149928006.accept(Unknown Source)
	at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
	at java.util.stream.IntPipeline$4$1.accept(IntPipeline.java:250)
	at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)
	at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
	at Test.main(Test.java:23)

Is it using a kind of Map that does not accept null values? Let's check:

   public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper) {
        return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
    } 

Well, no. It uses a standard HashMap. In the stack, the Exception is thrown from the HashMap.merge() function. So let's have a look:

   @Override
    public V merge(K key, V value,
                   BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        if (value == null)
            throw new NullPointerException();
	...
    }

So the problem is not in the type of map but in the implementation of the merge. The javadoc for the HashMap merge() method says that the value parameter is "the non-null value to be merged with the existing value associated with the key". So yes, it says it in the Javadoc, but not where you would expect it.

By the way, if you really want to make it work with streams, you would have to supply your own collector (as an aside note, the same problem arises with the groupBy collector) :

	Map<String, String> currentTeam = IntStream.range(0, player.size())
		.mapToObj(i -> i)
		.collect(HashMap::new,
			(map, i) -> map.put(player.get(i), team.get(i)),
			HashMap::putAll);

Maybe I'll stick with the loop for this time.